<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
     
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      
<section class="cover">
    
      
      <a class="forkMe" href="https://github.com/Shen-Yu/hexo-theme-ayer"
        target="_blank"><img width="149" height="149" src="/images/forkme.png"
          class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
    
  <div class="cover-frame">
    <div class="bg-box">
      <img src="/images/cover1.jpg" alt="image frame" />
    </div>
    <div class="cover-inner text-center text-white">
      <h1><a href="/">Hexo</a></h1>
      <div id="subtitle-box">
        
        <span id="subtitle"></span>
        
      </div>
      <div>
        
      </div>
    </div>
  </div>
  <div class="cover-learn-more">
    <a href="javascript:void(0)" class="anchor"><i class="ri-arrow-down-line"></i></a>
  </div>
</section>



<script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/typed.min.js"></script>


<!-- Subtitle -->

  <script>
    try {
      var typed = new Typed("#subtitle", {
        strings: ['面朝大海，春暖花开', '何来天才，唯有苦练', '集中一点，登峰造极'],
        startDelay: 0,
        typeSpeed: 200,
        loop: true,
        backSpeed: 100,
        showCursor: true
      });
    } catch (err) {
      console.log(err)
    }
  </script>
  
<div id="main">
  <section class="outer">
  
  

<div class="notice" style="margin-top:50px">
    <i class="ri-heart-fill"></i>
    <div class="notice-content" id="broad"></div>
</div>
<script type="text/javascript">
    fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
            document.getElementById("broad").innerHTML = data.hitokoto;
        })
        .catch(console.error)
</script>

<style>
    .notice {
        padding: 20px;
        border: 1px dashed #e6e6e6;
        color: #969696;
        position: relative;
        display: inline-block;
        width: 100%;
        background: #fbfbfb50;
        border-radius: 10px;
    }

    .notice i {
        float: left;
        color: #999;
        font-size: 16px;
        padding-right: 10px;
        vertical-align: middle;
        margin-top: -2px;
    }

    .notice-content {
        display: initial;
        vertical-align: middle;
    }
</style>
  
  <article class="articles">
    
    
    
    
    <article
  id="post-tips/我把 Python 的 200个标准库整理出来了"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/tips/%E6%88%91%E6%8A%8A%20Python%20%E7%9A%84%20200%E4%B8%AA%E6%A0%87%E5%87%86%E5%BA%93%E6%95%B4%E7%90%86%E5%87%BA%E6%9D%A5%E4%BA%86/"
    >我把 Python 的 200个标准库整理出来了.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/tips/%E6%88%91%E6%8A%8A%20Python%20%E7%9A%84%20200%E4%B8%AA%E6%A0%87%E5%87%86%E5%BA%93%E6%95%B4%E7%90%86%E5%87%BA%E6%9D%A5%E4%BA%86/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/tips/">tips</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="文本"><a href="#文本" class="headerlink" title="文本"></a>文本</h2><p>string：通用字符串操作</p>
<p>re：正则表达式操作</p>
<p>difflib：差异计算工具</p>
<p>textwrap：文本填充</p>
<p>unicodedata：Unicode字符数据库</p>
<p>stringprep：互联网字符串准备工具</p>
<p>readline：GNU按行读取接口</p>
<p>rlcompleter：GNU按行读取的实现函数</p>
<p>struct：将字节解析为打包的二进制数据</p>
<p>codecs：注册表与基类的编解码器</p>
<h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><p>datetime：基于日期与时间工具</p>
<p>calendar：通用月份函数</p>
<p>collections：容器数据类型</p>
<p>collections.abc：容器虚基类</p>
<p>heapq：堆队列算法</p>
<p>bisect：数组二分算法</p>
<p>array：高效数值数组</p>
<p>weakref：弱引用</p>
<p>types：内置类型的动态创建与命名</p>
<p>copy：浅拷贝与深拷贝</p>
<p>reprlib：交替repr()的实现</p>
<h2 id="数学"><a href="#数学" class="headerlink" title="数学"></a>数学</h2><p>numbers：数值的虚基类</p>
<p>math：数学函数</p>
<p>cmath：复数的数学函数</p>
<p>decimal：定点数与浮点数计算</p>
<p>fractions：有理数</p>
<p>random：生成伪随机数</p>
<h2 id="函数式编程"><a href="#函数式编程" class="headerlink" title="函数式编程"></a>函数式编程</h2><p>itertools：为高效循环生成迭代器</p>
<p>functools：可调用对象上的高阶函数与操作</p>
<p>operator：针对函数的标准操作</p>
<h2 id="文件与目录"><a href="#文件与目录" class="headerlink" title="文件与目录"></a>文件与目录</h2><p>os.path：通用路径名控制</p>
<p>fileinput：从多输入流中遍历行</p>
<p>stat：解释stat()的结果</p>
<p>filecmp：文件与目录的比较函数</p>
<p>tempfile：生成临时文件与目录</p>
<p>glob：Unix风格路径名格式的扩展</p>
<p>fnmatch：Unix风格路径名格式的比对</p>
<p>linecache：文本行的随机存储</p>
<p>shutil：高级文件操作</p>
<p>macpath：MacOS 9路径控制函数</p>
<h2 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h2><p>pickle：Python对象序列化</p>
<p>copyreg：注册机对pickle的支持函数</p>
<p>shelve：Python对象持久化</p>
<p>marshal：内部Python对象序列化</p>
<p>dbm：Unix“数据库”接口</p>
<p>sqlite3：针对SQLite数据库的API2.0</p>
<h2 id="压缩"><a href="#压缩" class="headerlink" title="压缩"></a>压缩</h2><p>zlib：兼容gzip的压缩</p>
<p>gzip：对gzip文件的支持</p>
<p>bz2：对bzip2压缩的支持</p>
<p>lzma：使用LZMA算法的压缩</p>
<p>zipfile：操作ZIP存档</p>
<p>tarfile：读写tar存档文件</p>
<h2 id="加密"><a href="#加密" class="headerlink" title="加密"></a>加密</h2><p>hashlib：安全散列与消息摘要</p>
<p>hmac：针对消息认证的键散列</p>
<h2 id="操作系统工具"><a href="#操作系统工具" class="headerlink" title="操作系统工具"></a>操作系统工具</h2><p>os：多方面的操作系统接口</p>
<p>io：流核心工具</p>
<p>time：时间的查询与转化</p>
<p>argparser：命令行选项、参数和子命令的解析器</p>
<p>optparser：命令行选项解析器</p>
<p>getopt：C风格的命令行选项解析器</p>
<p>logging：Python日志工具</p>
<p>logging.config：日志配置</p>
<p>logging.handlers：日志处理器</p>
<p>getpass：简易密码输入</p>
<p>curses：字符显示的终端处理</p>
<p>curses.textpad：curses程序的文本输入域</p>
<p>curses.ascii：ASCII字符集工具</p>
<p>curses.panel：curses的控件栈扩展</p>
<p>platform：访问底层平台认证数据</p>
<p>errno：标准错误记号</p>
<p>ctypes：Python外部函数库</p>
<h2 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h2><p>threading：基于线程的并行</p>
<p>multiprocessing：基于进程的并行</p>
<p>concurrent：并发包</p>
<p>concurrent.futures：启动并行任务</p>
<p>subprocess：子进程管理</p>
<p>sched：事件调度</p>
<p>queue：同步队列</p>
<p>select：等待I / O完成</p>
<p>dummy_threading：threading模块的替代（当_thread不可用时）</p>
<p>_thread：底层的线程API（threading基于其上）</p>
<p>_dummy_thread：_thread模块的替代（当_thread不可用时）</p>
<h2 id="进程间通信"><a href="#进程间通信" class="headerlink" title="进程间通信"></a>进程间通信</h2><p>socket：底层网络接口</p>
<p>ssl：socket对象的TLS / SSL填充器</p>
<p>asyncore：异步套接字处理器</p>
<p>asynchat：异步套接字命令 / 响应处理器</p>
<p>signal：异步事务信号处理器</p>
<p>mmap：内存映射文件支持</p>
<h2 id="互联网"><a href="#互联网" class="headerlink" title="互联网"></a>互联网</h2><p>email：邮件与MIME处理包</p>
<p>json：JSON编码与解码</p>
<p>mailcap：mailcap文件处理</p>
<p>mailbox：多种格式控制邮箱</p>
<p>mimetypes：文件名与MIME类型映射</p>
<p>base64：RFC3548：Base16、Base32、Base64编码</p>
<p>binhex：binhex4文件编码与解码</p>
<p>binascii：二进制码与ASCII码间的转化</p>
<p>quopri：MIMEquoted - printable数据的编码与解码</p>
<p>uu：uuencode文件的编码与解码</p>
<h2 id="互联网协议与支持"><a href="#互联网协议与支持" class="headerlink" title="互联网协议与支持"></a>互联网协议与支持</h2><p>webbrowser：简易Web浏览器控制器</p>
<p>cgi：CGI支持</p>
<p>cgitb：CGI脚本反向追踪管理器</p>
<p>wsgiref：WSGI工具与引用实现</p>
<p>urllib：URL处理模块</p>
<p>urllib.request：打开URL连接的扩展库</p>
<p>urllib.response：urllib模块的响应类</p>
<p>urllib.parse：将URL解析成组件</p>
<p>urllib.error：urllib.request引发的异常类</p>
<p>urllib.robotparser：robots.txt的解析器</p>
<p>http：HTTP模块</p>
<p>http.client：HTTP协议客户端</p>
<p>ftplib：FTP协议客户端</p>
<p>poplib：POP协议客户端</p>
<p>imaplib：IMAP4协议客户端</p>
<p>nntplib：NNTP协议客户端</p>
<p>smtplib：SMTP协议客户端</p>
<p>smtpd：SMTP服务器</p>
<p>telnetlib：Telnet客户端</p>
<p>uuid：RFC4122的UUID对象</p>
<p>socketserver：网络服务器框架</p>
<p>http.server：HTTP服务器</p>
<p>http.cookies：HTTPCookie状态管理器</p>
<p>http.cookiejar：HTTP客户端的Cookie处理</p>
<p>xmlrpc：XML - RPC服务器和客户端模块</p>
<p>xmlrpc.client：XML - RPC客户端访问</p>
<p>xmlrpc.server：XML - RPC服务器基础</p>
<p>ipaddress：IPv4 / IPv6控制库</p>
<h2 id="多媒体"><a href="#多媒体" class="headerlink" title="多媒体"></a>多媒体</h2><p>audioop：处理原始音频数据</p>
<p>aifc：读写AIFF和AIFC文件</p>
<p>sunau：读写Sun AU文件</p>
<p>wave：读写WAV文件</p>
<p>chunk：读取IFF大文件</p>
<p>colorsys：颜色系统间转化</p>
<p>imghdr：指定图像类型</p>
<p>sndhdr：指定声音文件类型</p>
<p>ossaudiodev：访问兼容OSS的音频设备</p>
<h2 id="国际化"><a href="#国际化" class="headerlink" title="国际化"></a>国际化</h2><p>gettext：多语言的国际化服务</p>
<p>locale：国际化服务</p>
<h2 id="编程框架"><a href="#编程框架" class="headerlink" title="编程框架"></a>编程框架</h2><p>turtle：Turtle图形库</p>
<p>cmd：基于行的命令解释器支持</p>
<p>shlex：简单词典分析</p>
<h2 id="Tk图形用户接口"><a href="#Tk图形用户接口" class="headerlink" title="Tk图形用户接口"></a>Tk图形用户接口</h2><p>tkinter：Tcl / Tk接口</p>
<p>tkinter.ttk：Tk主题控件</p>
<p>tkinter.tix：Tk扩展控件</p>
<p>tkinter.scrolledtext：滚轴文本控件</p>
<h2 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h2><p>pydoc：文档生成器和在线帮助系统</p>
<p>doctest：交互式Python示例</p>
<p>unittest：单元测试框架</p>
<p>unittest.mock：模拟对象库</p>
<p>test：Python回归测试包</p>
<p>test.support：Python测试工具套件</p>
<p>venv：虚拟环境搭建</p>
<h2 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h2><p>bdb：调试框架</p>
<p>faulthandler：Python反向追踪库</p>
<p>pdb：Python调试器</p>
<p>timeit：小段代码执行时间测算</p>
<p>trace：Python执行状态追踪</p>
<h2 id="运行时"><a href="#运行时" class="headerlink" title="运行时"></a>运行时</h2><p>sys：系统相关的参数与函数</p>
<p>sysconfig：访问Python配置信息</p>
<p>builtins：内置对象</p>
<p>main：顶层脚本环境</p>
<p>warnings：警告控制</p>
<p>contextlib：with状态的上下文工具</p>
<p>abc：虚基类</p>
<p>atexit：出口处理器</p>
<p>traceback：打印或读取一条栈的反向追踪</p>
<p>future：未来状态定义</p>
<p>gc：垃圾回收接口</p>
<p>inspect：检查存活的对象</p>
<p>site：址相关的配置钩子（hook）</p>
<p>fpectl：浮点数异常控制</p>
<p>distutils：生成和安装Python模块</p>
<h2 id="解释器"><a href="#解释器" class="headerlink" title="解释器"></a>解释器</h2><p>code：基类解释器</p>
<p>codeop：编译Python代码</p>
<h2 id="导入模块"><a href="#导入模块" class="headerlink" title="导入模块"></a>导入模块</h2><p>imp：访问import模块的内部</p>
<p>zipimport：从ZIP归档中导入模块</p>
<p>pkgutil：包扩展工具</p>
<p>modulefinder：通过脚本查找模块</p>
<p>runpy：定位并执行Python模块</p>
<p>importlib：import的一种实施</p>
<h2 id="Python语言"><a href="#Python语言" class="headerlink" title="Python语言"></a>Python语言</h2><p>parser：访问Python解析树</p>
<p>ast：抽象句法树</p>
<p>symtable：访问编译器符号表</p>
<p>symbol：Python解析树中的常量</p>
<p>token：Python解析树中的常量</p>
<p>keyword：Python关键字测试</p>
<p>tokenize：Python源文件分词</p>
<p>tabnany：模糊缩进检测</p>
<p>pyclbr：Python类浏览支持</p>
<p>py_compile：编译Python源文件</p>
<p>compileall：按字节编译Python库</p>
<p>dis：Python字节码的反汇编器</p>
<p>pickletools：序列化开发工具</p>
<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>formatter：通用格式化输出</p>
<h2 id="Windows相关"><a href="#Windows相关" class="headerlink" title="Windows相关"></a>Windows相关</h2><p>msilib：读写Windows的Installer文件</p>
<p>msvcrt：MS VC + + Runtime的有用程序</p>
<p>winreg：Windows注册表访问</p>
<p>winsound：Windows声音播放接口</p>
<h2 id="Unix相关"><a href="#Unix相关" class="headerlink" title="Unix相关"></a>Unix相关</h2><p>posix：最常用的POSIX调用</p>
<p>pwd：密码数据库</p>
<p>spwd：影子密码数据库</p>
<p>grp：组数据库</p>
<p>crypt：Unix密码验证</p>
<p>termios：POSIX风格的tty控制</p>
<p>tty：终端控制函数</p>
<p>pty：伪终端工具</p>
<p>fcntl：系统调用fcntl()和ioctl()</p>
<p>pipes：shell管道接口</p>
<p>resource：资源可用信息</p>
<p>nis：Sun的NIS的接口</p>
<p>syslog：Unix 日志服务</p>
<p>以上就是对Python内置各个模块的简要总结，大家可收藏此文，后面做项目时再从中检索。希望此文能帮助到大家，欢迎补充还常用的Python内置模块。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/tips/" rel="tag">tips</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-tips/推荐这22款高颜值的终端工具"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/tips/%E6%8E%A8%E8%8D%90%E8%BF%9922%E6%AC%BE%E9%AB%98%E9%A2%9C%E5%80%BC%E7%9A%84%E7%BB%88%E7%AB%AF%E5%B7%A5%E5%85%B7/"
    >推荐这22款高颜值的终端工具.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/tips/%E6%8E%A8%E8%8D%90%E8%BF%9922%E6%AC%BE%E9%AB%98%E9%A2%9C%E5%80%BC%E7%9A%84%E7%BB%88%E7%AB%AF%E5%B7%A5%E5%85%B7/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/tips/">tips</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="推荐这22款高颜值的终端工具"><a href="#推荐这22款高颜值的终端工具" class="headerlink" title="推荐这22款高颜值的终端工具"></a>推荐这22款高颜值的终端工具</h1><p>程序员在搞开发时，终端CLI工具的使用必不可少.</p>
<p>终端CLI 最重要的是什么?</p>
<p>生产力 ? 错, 是颜值. 高颜值的终端工具, 让 UI 小妹妹都爱上了学编程, 真的, 每天下班都嚷着让我叫她写代码, 烦 …</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvaTdW5yiaibnicclBZa33TUzjibF4lbfPLITrezyIjM8spia2HiaGvucicacfg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>高频 CLI 工具推荐</p>
<h2 id="1、fish-shell"><a href="#1、fish-shell" class="headerlink" title="1、fish shell"></a>1、fish shell</h2><p>毋庸置疑，在终端工具中，Shell 是使用最频繁也最重要的工具。过去，我曾经使用过 Bash 和 Z Shell，而如今，这个Fish Shell，也是一个非常优秀的终端 Shell 工具，拥有许多开箱即用的功能，例如语法自动推荐补全、语法高亮显示或使用快捷键在最近访问的文件夹之间来回切换。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjv3hpuTciaZGOvEaLveMxeuQz1LpcBghIClKsHUPMPHlv03TdjxCpibZZQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<h2 id="2、Starship"><a href="#2、Starship" class="headerlink" title="2、Starship"></a>2、Starship</h2><p>如果必须要从本篇文章中选择一个我最喜欢的终端工具——那非 Starship 莫属。Starship 可以适用于任何 Shell。你只需要安装它，然后在相应的配置文件.bashrc/.zshrc/config.fish添加一行配置，剩下的工作交给它来完成就好了。</p>
<p><img src=""></p>
<p>它可以做到：</p>
<ul>
<li><p>根据你是否在代码仓库中添加了新文件、是否修改了文件、是否暂存了文件等情况，用相应的符号表示 git 仓库的状态。</p>
</li>
<li><p>根据你所在的 Python 项目目录，展示 Python 的版本号，这也适用于 Go/Node/Rust/Elm 等其他编程语言环境。</p>
</li>
<li><p>展示上一个命令执行所用的时间，指令运行时间必须在毫秒级别。</p>
</li>
<li><p>如果上一个命令执行失败，会展示相应的错误提示符。</p>
</li>
</ul>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvxn2PlkwBb64iasdyj1mTMZGz4k08RTQwxIia2ia6Geib1EMia7ekO3W0Peg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>还有不计其数的其他信息可以展示。但是，它能以更加友好的形式智能地给你呈现！比如，如果你不在 git 存储库中，它将隐藏 git 信息。如果您不在 Python 项目中，则不会有 Python 版本信息，因为显示它没有什么意义。它永远不会给你展示多余信息，始终保持终端的美观，优雅和简约。</p>
<h2 id="3、z"><a href="#3、z" class="headerlink" title="3、z"></a>3、z</h2><p>“z” 可以让你快速地在文件目录之间跳转。它会记住你访问的历史文件夹，经过短暂的学习后，你就可以使用z path_of_the_folder_name命令在目录之间跳转了。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvUchV2xicGx0UiafUia7B7dEg9StMOhwIibs0Hv34Me6RF6zyEKqYnFDstQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>比如，如果我经常访问 ~/work/src/projects，我只需要运行 z pro ，就可以立马跳转到那里。z 的原理参考了 frecency 算法——一个基于统计 frequency 和 recency 进行分析的算法。如果它存储了你不想使用的路径文件夹，你随时可以手动将其删除。它提高了我在常用的不同文件路径之间频繁切换的效率，帮我节省了键盘击键次数以及大量的路径记忆。</p>
<h2 id="4、fzf"><a href="#4、fzf" class="headerlink" title="4、fzf"></a>4、fzf</h2><p>fzf — fuzzy finder，即模糊查找器。它是一种通用工具，可让你使用模糊搜索来查找文件、历史命令、进程、git 提交等。你键入一些字母，它会尝试匹配结果列表中任何位置的字母。输入的字母越多，结果也就越准确。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvpXlnKpXQPzrkmdzMolwkzz7fpAqK1LqZP87rR3Vfg23I4FbOqU33aQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<h2 id="5、fd"><a href="#5、fd" class="headerlink" title="5、fd"></a>5、fd</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjv6yEj3YURHfhOBy7L0wNB4qCccDU9Nfqw0UQzt8a2eLgI0BwMVogZJw/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>上面动图是 find 命令（左）和 fd 命令（右）的使用对比。</p>
<p>类似于系统自带的 find 命令，但使用起来更简单，查找速度更快，并且具有良好的默认设置。</p>
<p>比如你想找一个名为“invoice”的文件，但是不确定文件的扩展名，还是查找一个存放所有 invoice 的目录，而不单是一个文件。你可以撸起袖子，开始为 find 命令编写那些复杂的正则表达式，也可以直接命令行运行 fd invoice。</p>
<p>默认情况下，fd 会忽略隐藏的以及在.gitignore列出的文件和目录。大多数时候，这也是我们想要的，但是在极少数特殊情况下，如果需要禁用此功能时，我会给该命令设置一个别名：fda=’fd -IH’。</p>
<p>你会发现，fd 命令输出的颜色配置很漂亮，而且根据基准测试（上述 GIF），它的执行速度甚至比find 命令的还要快。</p>
<h2 id="6、ripgrep"><a href="#6、ripgrep" class="headerlink" title="6、ripgrep"></a>6、ripgrep</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvkvcXnRLuBFbCN2lhveHIIPWEiagiaZB8bJk5WyJD6hLbMTvibMyDPucAQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1">这22款高颜值的终端工具，点燃了萌妹对编程的热爱</p>
<p>上图为 grep（左）与 rg（右）命令执行时的对比。</p>
<p>与上述fd指令类似，ripgrep是grep命令的替代方法， 不过ripgrep的执行速度更快，而且具有健全的默认配置以及丰富的彩色输出。</p>
<h2 id="7、htop-和-glances"><a href="#7、htop-和-glances" class="headerlink" title="7、htop 和 glances"></a>7、htop 和 glances</h2><p>在 Linux 或 Mac 上显示进程运行状态信息最常用工具是我们熟悉的top，它是每位系统管理员的好帮手。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvyWCEicBpKJbRXdlWfAEhLpT3ZicdPO3VUCp6FHWfkHpvxib4qksb9nslQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>htop工具是top工具的绝佳替代品。</p>
<p>top工具是非常基础的监控工具，提供的功能有限，因此很多人转去使用 htop。htop比起top，优势很明显——除了功能更加完善以外，它的色彩搭配也很丰富，整体上使用起来更加友好。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjv3WClvRghhHP5p3icYwKJprDEeibRtc0IAaCWV2uicOEOJibEUTX9J6z0Nw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>借助 glances，还可以让你一目了然地快速了解系统当前状态。</p>
<p>glances 是htop的补充工具。除了列出所有进程及其 CPU 和内存使用情况之外，它还可以显示有关系统的其他信息，比如：</p>
<ul>
<li><p>网络及磁盘使用情况</p>
</li>
<li><p>文件系统已使用的空间和总空间</p>
</li>
<li><p>来自不同传感器（例如电池）的数据</p>
</li>
<li><p>以及最近消耗过多资源的进程列表</p>
</li>
</ul>
<h2 id="8、virtualenv-和-virtualfish"><a href="#8、virtualenv-和-virtualfish" class="headerlink" title="8、virtualenv 和 virtualfish"></a>8、virtualenv 和 virtualfish</h2><p>Virtualenv 是用于在 Python 中创建虚拟环境的工具。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvyQlxicP4iaI5cMhawiantyTC1mINfibcTyFoEhdLdlEoBzhYqpcicWDf3wQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>VirtualFish 则是 Fish Shell 的虚拟环境管理器。它提供了许多命令来执行快速创建、列出或删除虚拟环境等操作。</p>
<h2 id="9、pyenv、nodenv-和-rbenv"><a href="#9、pyenv、nodenv-和-rbenv" class="headerlink" title="9、pyenv、nodenv 和 rbenv"></a>9、pyenv、nodenv 和 rbenv</h2><p>pyenv 可以轻松实现 Python 版本的切换。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjv6vEibUDnkwWvk38qJMXLic8AT9mIzjXzib8XOhicKFod818W5b1OyYksIQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>Pyenv、nodenv 和 rubyenv 是用于管理计算机上不同版本的 Python、Node 和 Ruby 的工具。</p>
<p>假设你要在计算机上安装两个版本的 Python。比如，你正在从事两个不同的 Python 项目，或者因为特殊情况仍然需要使用 Python2。不同 Python 版本在电脑上管理很复杂。你需要确保不同的项目具有正确版本的软件依赖包。如果不小心，很容易弄乱这种脆弱的配置。</p>
<p>该工具为版本管理提供了很多帮助，并将这一噩梦变得易于管理。它可以全局或“按文件夹”切换 Python 版本，而且每个版本都是相互隔离的。</p>
<h2 id="10、pipx"><a href="#10、pipx" class="headerlink" title="10、pipx"></a>10、pipx</h2><p>Virtualenv 解决了 Python 程序包管理中的许多问题，但是还有一个方案可以解决。如果我想在全局环境下安装 Python 软件包（比如它是一个独立的工具，正如前面提到的glances 工具），那么我会遇到全局安装带来的问题。在虚拟环境之外安装软件包不是一个好主意，将来可能会导致意想不到的问题。另一方面，如果我决定使用虚拟环境，那么每次我要运行程序时都需要激活该虚拟环境。这也不是最方便的解决方案。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvVasCtolLS7QYZL5g8IIocOm4cVfSeZb6GeUDPLiagVZWlBRRUuQgyuA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>事实证明，pipx工具可以解决上面提到的问题。它将 Python 软件依赖包安装到单独的环境中（因此不会存在依赖项冲突的问题）。与此同时，这些工具提供的 CLI 命令在全局环境内也可用。因此，我无需激活任何环境——pipx会帮我完成这个操作！</p>
<h2 id="11、ctop-和-lazydocker"><a href="#11、ctop-和-lazydocker" class="headerlink" title="11、ctop 和 lazydocker"></a>11、ctop 和 lazydocker</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvNOlTMEEq8EmfDJcfnKfGiaQYibSSxyRL30pdMWYh8yfIiahYRQ9UiaMGQQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>ctop 的实时监控示例</p>
<p>当你使用 Docker 并对其监控时，这两个工具会很有帮助。ctop是 Docker 容器的顶级接口。它可以为你：</p>
<ul>
<li><p>展示正在运行和已停止的容器列表。</p>
</li>
<li><p>展示统计信息，例如内存、CPU 使用率以及针对每个容器的其他详细信息窗口（例如绑定的端口等其他信息）。</p>
</li>
<li><p>提供快捷菜单，方便快速停止、杀掉指定容器进程或显示给定容器的日志。</p>
</li>
</ul>
<p>这比你尝试从docker ps命令中找出所有这些信息要方便多了。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvt3GCjic2fGlPFGgMcJoaCcJ8OwrZgfQn6tibX0STfn40OYnEHP4p15fg/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>lazydocker也是一个非常好用的 Docker 工具</p>
<p>lazydocker它是一个非常成熟的拥有终端 UI 界面的工具，提供了非常丰富的功能用于管理 Docker。</p>
<h2 id="12、Homebrew"><a href="#12、Homebrew" class="headerlink" title="12、Homebrew"></a>12、Homebrew</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjv2ibV1wPgMiaEeIhQQ4jPRb3ACMumwKCbN56cicWRyxiaEsuwzYKTMtb6Bg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>如果使用的是 Mac，那就无需再多介绍 Homebrew 了。它是 macOS 上被业界普遍认可的软件包管理器。</p>
<h2 id="13、asciinema"><a href="#13、asciinema" class="headerlink" title="13、asciinema"></a>13、asciinema</h2><p><img src=""></p>
<p>asciinema是可用于记录终端会话的工具。但是，与录制 GIF 不同，它可以让用户选择并复制这些录制中的代码！这对于录制编码教程来说十分好用。asciinema录制的内容，支持直接复制，十分给力。</p>
<blockquote>
<p>“</p>
<p>下载地址：<a target="_blank" rel="noopener" href="https://asciinema.org/">https://asciinema.org/</a></p>
<p>”</p>
</blockquote>
<h2 id="14、colordiff-和-diff-so-fancy"><a href="#14、colordiff-和-diff-so-fancy" class="headerlink" title="14、colordiff 和 diff-so-fancy"></a>14、colordiff 和 diff-so-fancy</h2><p><img src=""></p>
<p>如果想在终端中使用diff操作（比较两个文件之间的差异），可以放弃使用diff命令，而是使用 colordiff。colordiff输出可以高亮显示，因此在查看文件差异内容时要方便得多，而不是在diff命令输出内容下，费力地查看所有的“ &lt;”和“&gt;”符号来对比文件差异。</p>
<p>如果你觉得还不够，那么推荐 diff-so-fancy。它是比colordiff更友好的一个差异对比工具。</p>
<p><img src=""></p>
<p>它通过以下方式进一步改善了文件内容差异展示的外观：</p>
<ul>
<li><p>突出显示每一行中差异的单词，而不是整行</p>
</li>
<li><p>简化变更文件的标题</p>
</li>
<li><p>去除 + 和 - 符号（颜色差异展示就够了）</p>
</li>
<li><p>清楚地指出新行和删除的空行</p>
</li>
</ul>
<h2 id="15、tree"><a href="#15、tree" class="headerlink" title="15、tree"></a>15、tree</h2><p>可以通过brew install tree安装该工具。如果要查看给定目录的内容，那么 tree 是执行此操作的必备工具。它能以漂亮的树状结构显示所有子目录及文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ tree .    </span><br><span class="line">.    </span><br><span class="line">├── recovery.md    </span><br><span class="line">├── README.md    </span><br><span class="line">├── archive    </span><br><span class="line">├── automator    </span><br><span class="line">│   ├── Open\ Iterm2.workflow    </span><br><span class="line">│   │   └── Contents    </span><br><span class="line">│   │       ├── Info.plist    </span><br><span class="line">│   │       ├── QuickLook    </span><br><span class="line">│   │       │   └── Thumbnail.png    </span><br><span class="line">│   │       └── document.wflow    </span><br><span class="line">│   └── Start\ Screen\ Saver.workflow    </span><br><span class="line">├── brew-cask.sh </span><br></pre></td></tr></table></figure>

<h2 id="16、bat"><a href="#16、bat" class="headerlink" title="16、bat"></a>16、bat</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvgkoicYIQZunhlg4RUqzxZ4XbbgJ3RzF3Cq2BWpADDSaHX1HYZ0NrtibA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>类似于在终端中常用的用于显示文件内容的cat命令，但是bat效果更佳。</p>
<p>它增加了语法高亮显示，git gutter 标记（如果适用），自动分页（如果文件很大）等功能，并且使得输出的内容阅读起来更加友好。</p>
<h2 id="17、httpie"><a href="#17、httpie" class="headerlink" title="17、httpie"></a>17、httpie</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjv9kAfYF1sBwMc7qmw3uFTw84I66yoSfP9uugicPTHYNRh8L3kSZIjXpQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>如果你需要发送一些 HTTP 请求，但发现使用curl不够直观，那么可以尝试一下httpie。这是一款非常好用的curl替代工具。合理的默认配置以及简洁的语法使它更易于使用，命令返回也是彩色输出。</p>
<h2 id="18、tldr"><a href="#18、tldr" class="headerlink" title="18、tldr"></a>18、tldr</h2><p>简化版的命令帮助手册。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvpHUkvP0HxQiaWedcYQqsWvb1koWicHldYcpOcb5t3rpJTKvRE0gzlkJw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"></p>
<p>“man pages”展示的信息太全面了，但是很多时候使用tldr可以更快地帮你找到特定信息，这才是用户真正想要的。</p>
<h2 id="19、exa"><a href="#19、exa" class="headerlink" title="19、exa"></a>19、exa</h2><p><img src=""></p>
<p>exa是ls命令的一个可替代方案。</p>
<p>它色彩艳丽，还可以显示 git 状态等其他信息，自动将文件大小转换为方便人们阅读的单位，并且所有这些都保持与ls几乎相同的执行速度。</p>
<h2 id="20、litecli-和-pgcli"><a href="#20、litecli-和-pgcli" class="headerlink" title="20、litecli 和 pgcli"></a>20、litecli 和 pgcli</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvibFejPsjM412Fe2nzje6olw3BbV2Ae4wInHpxXG2xycr7nkndr8Gnicg/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>这是SQLite 和 PostgreSQL CLI 的解决方案。借助自动提示和语法突出显示，它们比默认的sqlite3和psql工具要好用很多。</p>
<h2 id="21、mas"><a href="#21、mas" class="headerlink" title="21、mas"></a>21、mas</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvkvcXnRLuBFbCN2lhveHIIPWEiagiaZB8bJk5WyJD6hLbMTvibMyDPucAQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>mas是一个用于从 App Store 安装软件的 CLI 工具。</p>
<h2 id="22、ncdu"><a href="#22、ncdu" class="headerlink" title="22、ncdu"></a>22、ncdu</h2><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/eukZ9J6BEiad3jiaYj1EYzznR3LAhjvnjvkvcXnRLuBFbCN2lhveHIIPWEiagiaZB8bJk5WyJD6hLbMTvibMyDPucAQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1"></p>
<p>这是在终端进行磁盘分析时使用的工具，它使用起来简单快捷。当需要释放一些硬盘空间时，可以使用这款工具。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/tips/" rel="tag">tips</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-tips/有了这款可视化工具JVisualVM ，Java 应用性能调优"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/tips/%E6%9C%89%E4%BA%86%E8%BF%99%E6%AC%BE%E5%8F%AF%E8%A7%86%E5%8C%96%E5%B7%A5%E5%85%B7JVisualVM%20%EF%BC%8CJava%20%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"
    >有了这款可视化工具JVisualVM ，Java 应用性能调优.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/tips/%E6%9C%89%E4%BA%86%E8%BF%99%E6%AC%BE%E5%8F%AF%E8%A7%86%E5%8C%96%E5%B7%A5%E5%85%B7JVisualVM%20%EF%BC%8CJava%20%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/tips/">tips</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="有了这款可视化工具JVisualVM-，Java-应用性能调优"><a href="#有了这款可视化工具JVisualVM-，Java-应用性能调优" class="headerlink" title="有了这款可视化工具JVisualVM ，Java 应用性能调优"></a>有了这款可视化工具JVisualVM ，Java 应用性能调优</h1><ul>
<li>JVisualVM 简介</li>
<li>案例分析</li>
<li>准备模拟内存泄漏样例</li>
<li>使用JVisualVM分析内存泄漏</li>
<li>JVisualVM 远程监控 Tomcat</li>
</ul>
<h1 id="JVisualVM-简介"><a href="#JVisualVM-简介" class="headerlink" title="JVisualVM 简介"></a>JVisualVM 简介</h1><p>VisualVM 是Netbeans的profile子项目，已在JDK6.0 update 7 中自带，能够监控线程，内存情况，查看方法的CPU时间和内存中的对 象，已被GC的对象，反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下面，有一个jvisualvm.exe文件，双击打开，从UI上来看，这个软件是基于NetBeans开发的了。</p>
<p>VisualVM 提供了一个可视界面，用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序的详细信息。VisualVM 对 Java Development Kit (JDK) 工具所检索的 JVM 软件相关数据进行组织，并通过一种使您可以快速查看有关多个 Java 应用程序的数据的方式提供该信息。您可以查看本地应用程序或远程主机上运行的应用程序的相关数据。此外，还可以捕获有关 JVM 软件实例的数据，并将该数据保存到本地系统，以供后期查看或与其他用户共享。</p>
<p>双击启动 jvisualvm.exe，启动起来后和jconsole 一样同样可以选择本地和远程，如果需要监控远程同样需要配置相关参数。</p>
<p>主界面如下；</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172608491.webp" alt="img"></p>
<p>VisualVM可以根据需要安装不同的插件，每个插件的关注点都不同，有的主要监控GC，有的主要监控内存，有的监控线程等。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605298.webp" alt="img"></p>
<p>如何安装：</p>
<blockquote>
<p>1、从主菜单中选择“工具”&gt;“插件”。2、在“可用插件”标签中，选中该插件的“安装”复选框。单击“安装”。3、逐步完成插件安装程序。</p>
</blockquote>
<p>我这里以 Eclipse(pid 22296)为例，双击后直接展开，主界面展示了系统和jvm两大块内容，点击右下方jvm参数和系统属性可以参考详细的参数信息.</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605550.webp" alt="img"></p>
<p>因为VisualVM的插件太多，我这里主要介绍三个我主要使用几个：监控、线程、Visual GC</p>
<p>监控的主页其实也就是，cpu、内存、类、线程的图表</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605297.webp" alt="img"></p>
<p>线程和jconsole功能没有太大的区别</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605464.webp" alt="img"></p>
<p>Visual GC 是常常使用的一个功能，可以明显的看到年轻代、老年代的内存变化，以及gc频率、gc的时间等。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172627308.webp" alt="img"></p>
<p>以上的功能其实jconsole几乎也有，VisualVM更全面更直观一些，另外VisualVM非常多的其它功能，可以分析dump的内存快照，</p>
<p>dump出来的线程快照并且进行分析等，还有其它很多的插件大家可以去探索</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605376.webp" alt="img"></p>
<h1 id="案例分析"><a href="#案例分析" class="headerlink" title="案例分析"></a>案例分析</h1><h2 id="准备模拟内存泄漏样例"><a href="#准备模拟内存泄漏样例" class="headerlink" title="准备模拟内存泄漏样例"></a>准备模拟内存泄漏样例</h2><p>1、定义静态变量HashMap</p>
<p>2、分段循环创建对象，并加入HashMap</p>
<p>代码如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">&#96;import java.util.HashMap;  </span><br><span class="line">import java.util.Map;  </span><br><span class="line">public class CyclicDependencies &#123;  </span><br><span class="line">    &#x2F;&#x2F;声明缓存对象  </span><br><span class="line">    private static final Map map &#x3D; new HashMap();  </span><br><span class="line">    public static void main(String args[])&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Thread.sleep(10000);&#x2F;&#x2F;给打开visualvm时间  </span><br><span class="line">        &#125; catch (InterruptedException e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">        &#125;  </span><br><span class="line">        &#x2F;&#x2F;循环添加对象到缓存  </span><br><span class="line">        for(int i&#x3D;0; i&lt;1000000;i++)&#123;  </span><br><span class="line">            TestMemory t &#x3D; new TestMemory();  </span><br><span class="line">            map.put(&quot;key&quot;+i,t);  </span><br><span class="line">        &#125;  </span><br><span class="line">        System.out.println(&quot;first&quot;);  </span><br><span class="line">        &#x2F;&#x2F;为dump出堆提供时间  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Thread.sleep(10000);  </span><br><span class="line">        &#125; catch (InterruptedException e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">        &#125;  </span><br><span class="line">        for(int i&#x3D;0; i&lt;1000000;i++)&#123;  </span><br><span class="line">            TestMemory t &#x3D; new TestMemory();  </span><br><span class="line">            map.put(&quot;key&quot;+i,t);  </span><br><span class="line">        &#125;  </span><br><span class="line">        System.out.println(&quot;second&quot;);  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Thread.sleep(10000);  </span><br><span class="line">        &#125; catch (InterruptedException e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">        &#125;  </span><br><span class="line">        for(int i&#x3D;0; i&lt;3000000;i++)&#123;  </span><br><span class="line">            TestMemory t &#x3D; new TestMemory();  </span><br><span class="line">            map.put(&quot;key&quot;+i,t);  </span><br><span class="line">        &#125;  </span><br><span class="line">        System.out.println(&quot;third&quot;);  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Thread.sleep(10000);  </span><br><span class="line">        &#125; catch (InterruptedException e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">        &#125;  </span><br><span class="line">        for(int i&#x3D;0; i&lt;4000000;i++)&#123;  </span><br><span class="line">            TestMemory t &#x3D; new TestMemory();  </span><br><span class="line">            map.put(&quot;key&quot;+i,t);  </span><br><span class="line">        &#125;  </span><br><span class="line">        System.out.println(&quot;forth&quot;);  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Thread.sleep(Integer.MAX_VALUE);  </span><br><span class="line">        &#125; catch (InterruptedException e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">        &#125;  </span><br><span class="line">        System.out.println(&quot;qqqq&quot;);  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br><span class="line">&#96;</span><br></pre></td></tr></table></figure>

<p>3、配置jvm参数如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#96;         -Xms512m  </span><br><span class="line">         -Xmx512m  </span><br><span class="line">         -XX:-UseGCOverheadLimit  </span><br><span class="line">         -XX:MaxPermSize&#x3D;50m  </span><br><span class="line">&#96;</span><br></pre></td></tr></table></figure>

<p>4、运行程序并打卡visualvm监控</p>
<h2 id="使用JVisualVM分析内存泄漏"><a href="#使用JVisualVM分析内存泄漏" class="headerlink" title="使用JVisualVM分析内存泄漏"></a>使用JVisualVM分析内存泄漏</h2><p>1、查看Visual GC标签，内容如下，这是输出first的截图</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605790.png" alt="img"></p>
<p>这是输出forth的截图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605479.png" alt="img"></p>
<p>通过2张图对比发现：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605634.webp" alt="img"></p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605790.webp" alt="img"></p>
<p>老生代一直在gc，当程序继续运行可以发现老生代gc还在继续：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605788.webp" alt="img"></p>
<p>增加到了7次，但是老生代的内存并没有减少。说明存在无法被回收的对象，可能是内存泄漏了。</p>
<p>如何分析是那个对象泄漏了呢？打开抽样器标签：点击后如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605789.webp" alt="img"></p>
<p>按照程序输出进行堆dump，当输出second时，dump一次，当输出forth时dump一次。</p>
<p>进入最后dump出来的堆标签，点击类：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605377.webp" alt="img"></p>
<p>点击右上角：“与另一个堆存储对比”。如图选择第一次导出的dump内容比较：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605139.png" alt="img"></p>
<p>比较结果如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605634.png" alt="img"></p>
<p>可以看出在两次间隔时间内TestMemory对象实例一直在增加并且多了，说明该对象引用的方法可能存在内存泄漏。</p>
<p>如何查看对象引用关系呢？</p>
<p>右键选择类TestMemory，选择“在实例视图中显示”，如下所示：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605789.png" alt="img"></p>
<p>左侧是创建的实例总数，右侧上部为该实例的结构，下面为引用说明，从图中可以看出在类CyclicDependencies里面被引用了，并且被HashMap引用。</p>
<p>如此可以确定泄漏的位置，进而根据实际情况进行分析解决。</p>
<h2 id="JVisualVM-远程监控-Tomcat"><a href="#JVisualVM-远程监控-Tomcat" class="headerlink" title="JVisualVM 远程监控 Tomcat"></a>JVisualVM 远程监控 Tomcat</h2><p>1、修改远程tomcat的catalina.sh配置文件，在其中增加：</p>
<ol>
<li><p>JAVA_OPTS=”$JAVA_OPTS</p>
</li>
<li><p>-Djava.rmi.server.hostname=192.168.122.128</p>
</li>
<li><p>-Dcom.sun.management.jmxremote.port=18999</p>
</li>
<li><p>-Dcom.sun.management.jmxremote.ssl=false</p>
</li>
<li><p>-Dcom.sun.management.jmxremote.authenticate=false”</p>
<p>这次配置先不走权限校验。只是打开jmx端口。</p>
<p>2、打开jvisualvm，右键远程，选择添加远程主机：</p>
</li>
</ol>
<p><img src="http://iubest.gitee.io/pic/640-1601172605786.webp" alt="img"></p>
<p>3、输入主机的名称，直接写ip，如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601172605391.webp" alt="img"></p>
<p>右键新建的主机，选择添加JMX连接，输入在tomcat中配置的端口即可。</p>
<p>4、双击打开。完毕！</p>
<h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/kl28978113/article/details/53817827">https://blog.csdn.net/kl28978113/article/details/53817827</a></p>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/ityouknow/p/6437037.html">https://www.cnblogs.com/ityouknow/p/6437037.html</a></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/tips/" rel="tag">tips</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-tips/用动图展示10大Git命令"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/tips/%E7%94%A8%E5%8A%A8%E5%9B%BE%E5%B1%95%E7%A4%BA10%E5%A4%A7Git%E5%91%BD%E4%BB%A4/"
    >用动图展示10大Git命令.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/tips/%E7%94%A8%E5%8A%A8%E5%9B%BE%E5%B1%95%E7%A4%BA10%E5%A4%A7Git%E5%91%BD%E4%BB%A4/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/tips/">tips</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="用动图展示10大Git命令"><a href="#用动图展示10大Git命令" class="headerlink" title="用动图展示10大Git命令"></a>用动图展示10大Git命令</h1><p><strong>作者：Lydia Hallie</strong></p>
<p><em>原文链接：<a target="_blank" rel="noopener" href="https://dev.to/lydiahallie/cs-visualized-useful-git-commands-37p1">https://dev.to/lydiahallie/cs-visualized-useful-git-commands-37p1</a></em></p>
<blockquote>
<p>git merge、git rebase、git reset、git revert、git fetch、git pull、git reflog……你知道这些 git 命令执行的究竟是什么任务吗？如果你还有些分不清楚，那千万不能错过这篇文章。在本文中，熟知 JavaScript、TypeScript、GraphQL、Serverless、AWS、Docker 和 Golang 的 21 岁年轻软件顾问 Lydia Hallie 通过动图形式直观地介绍了这些常用 git 命令的工作过程，包你过目不忘。</p>
</blockquote>
<p>尽管 Git 是一款非常强大的工具，但如果我说 Git 用起来简直是噩梦，大多数人也会认同我的说法。我发现在使用 Git 时，在头脑里可视化地想象它会非常有用：当我执行一个特定命令时，这些分支会如何交互，又会怎样影响历史记录？为什么当我在 master 上执行硬重启，force push 到原分支以及 rimraf 我们的 .git 文件夹时，我的同事哭了？</p>
<p>我觉得创建一些最常用且最有用的 Git 命令的可视化示例会是一个完美的用例！下面我将介绍的很多命令都有可选参数——你可以使用这些参数来改变对应命令的行为。而我的示例只会涵盖命令的默认行为，而不会添加（或添加太多）可选配置！</p>
<h2 id="合并"><a href="#合并" class="headerlink" title="合并"></a><strong>合并</strong></h2><p>拥有多个分支是很方便的，这样可以将不同的新修改互相隔离开，而且还能确保你不会意外地向生产代码推送未经许可或破损的代码修改。但一旦这些修改得到了批准许可，我们就需要将其部署到我们的生产分支中！</p>
<p>可将一个分支的修改融入到另一个分支的一种方式是执行 git merge。Git 可执行两种类型的合并：fast-forward 和 no-fast-forward。现在你可能分不清，但我们马上就来看看它们的差异所在。</p>
<p><strong>Fast-forward (—ff)</strong></p>
<p>在当前分支相比于我们要合并的分支没有额外的提交（commit）时，可以执行 fast-forward 合并。Git 很懒，首先会尝试执行最简单的选项：fast-forward！这类合并不会创建新的提交，而是会将我们正在合并的分支上的提交直接合并到当前分支。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590081.gif" alt="img"></p>
<p>完美！现在，我们在 dev 分支上所做的所有改变都合并到了 master 分支上。那么 no-fast-forward 又是什么意思呢？</p>
<p><strong>No-fast-foward (—no-ff)</strong></p>
<p>如果你的当前分支相比于你想要合并的分支没有任何提交，那当然很好，但很遗憾现实情况很少如此！如果我们在当前分支上提交我们想要合并的分支不具备的改变，那么 git 将会执行 no-fast-forward 合并。</p>
<p>使用 no-fast-forward 合并时，Git 会在当前活动分支上创建新的 merging commit。这个提交的父提交（parent commit）即指向这个活动分支，也指向我们想要合并的分支！</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170589732.gif" alt="img"></p>
<p>没什么大不了的，完美的合并！现在，我们在 dev 分支上所做的所有改变都合并到了 master 分支上。</p>
<h2 id="合并冲突"><a href="#合并冲突" class="headerlink" title="合并冲突"></a><strong>合并冲突</strong></h2><p>尽管 Git 能够很好地决定如何合并分支以及如何向文件添加修改，但它并不总是能完全自己做决定。当我们想要合并的两个分支的同一文件中的同一行代码上有不同的修改，或者一个分支删除了一个文件而另一个分支修改了这个文件时，Git 就不知道如何取舍了。</p>
<p>在这样的情况下，Git 会询问你想要保留哪种选择？假设在这两个分支中，我们都编辑了 README.md 的第一行。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170589971.png" alt="img"></p>
<p>如果我们想把 dev 合并到 master，就会出现一个合并冲突：你想要标题是 Hello! 还是 Hey!？</p>
<p>当尝试合并这些分支时，Git 会向你展示冲突出现的位置。我们可以手动移除我们不想保留的修改，保存这些修改，再次添加这个已修改的文件，然后提交这些修改。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170616359.gif" alt="img"></p>
<p>完成！尽管合并冲突往往很让人厌烦，但这是合理的：Git 不应该瞎猜我们想要保留哪些修改。</p>
<h2 id="变基（Rebasing）"><a href="#变基（Rebasing）" class="headerlink" title="变基（Rebasing）"></a><strong>变基（Rebasing）</strong></h2><p>我们刚看到可通过执行 git merge 将一个分支的修改应用到另一个分支。另一种可将一个分支的修改融入到另一个分支的方式是执行 git rebase。</p>
<p>git rebase 会将当前分支的提交复制到指定的分支之上。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170589930.gif" alt="img"></p>
<p>完美，现在我们在 dev 分支上获取了 master 分支上的所有修改。</p>
<p>变基与合并有一个重大的区别：Git 不会尝试确定要保留或不保留哪些文件。我们执行 rebase 的分支总是含有我们想要保留的最新近的修改！这样我们不会遇到任何合并冲突，而且可以保留一个漂亮的、线性的 Git 历史记录。</p>
<p>上面这个例子展示了在 master 分支上的变基。但是，在更大型的项目中，你通常不需要这样的操作。git rebase 在为复制的提交创建新的 hash 时会修改项目的历史记录。</p>
<p>如果你在开发一个 feature 分支并且 master 分支已经更新过，那么变基就很好用。你可以在你的分支上获取所有更新，这能防止未来出现合并冲突。</p>
<h2 id="交互式变基（Interactive-Rebase）"><a href="#交互式变基（Interactive-Rebase）" class="headerlink" title="交互式变基（Interactive Rebase）"></a><strong>交互式变基（Interactive Rebase）</strong></h2><p>在为提交执行变基之前，我们可以修改它们！我们可以使用交互式变基来完成这一任务。交互式变基在你当前开发的分支上以及想要修改某些提交时会很有用。</p>
<p>在我们正在 rebase 的提交上，我们可以执行以下 6 个动作：</p>
<ul>
<li>reword：修改提交信息；</li>
<li>edit：修改此提交；</li>
<li>squash：将提交融合到前一个提交中；</li>
<li>fixup：将提交融合到前一个提交中，不保留该提交的日志消息；</li>
<li>exec：在每个提交上运行我们想要 rebase 的命令；</li>
<li>drop：移除该提交。</li>
</ul>
<p>很棒！这样我们就能完全控制我们的提交了。如果你想要移除一个提交，只需 drop 即可。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590080.gif" alt="img"></p>
<p>如果你想把多个提交融合到一起以便得到清晰的提交历史，那也没有问题！</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590083.gif" alt="img"></p>
<p>交互式变基能为你在 rebase 时提供大量控制，甚至可以控制当前的活动分支。</p>
<h2 id="重置（Resetting）"><a href="#重置（Resetting）" class="headerlink" title="重置（Resetting）"></a><strong>重置（Resetting）</strong></h2><p>当我们不想要之前提交的修改时，就会用到这个命令。也许这是一个 WIP 提交或者可能是引入了 bug 的提交，这时候就要执行 git reset。</p>
<p>git reset 能让我们不再使用当前台面上的文件，让我们可以控制 HEAD 应该指向的位置。</p>
<p><strong>软重置</strong></p>
<p>软重置会将 HEAD 移至指定的提交（或与 HEAD 相比的提交的索引），而不会移除该提交之后加入的修改！</p>
<p>假设我们不想保留添加了一个 style.css 文件的提交 9e78i，而且我们也不想保留添加了一个 index.js 文件的提交 035cc。但是，我们确实又想要保留新添加的 style.css 和 index.js 文件！这是软重置的一个完美用例。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590081.gif" alt="img"></p>
<p>输入 git status 后，你会看到我们仍然可以访问在之前的提交上做过的所有修改。这很好，这意味着我们可以修复这些文件的内容，之后再重新提交它们！</p>
<p><strong>硬重置</strong></p>
<p>有时候我们并不想保留特定提交引入的修改。不同于软重置，我们应该再也无需访问它们。Git 应该直接将整体状态直接重置到特定提交之前的状态：这甚至包括你在工作目录中和暂存文件上的修改。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170625329.gif" alt="img"></p>
<p>Git 丢弃了 9e78i 和 035cc 引入的修改，并将状态重置到了 ec5be 的状态。</p>
<h2 id="还原（Reverting）"><a href="#还原（Reverting）" class="headerlink" title="还原（Reverting）"></a><strong>还原（Reverting）</strong></h2><p>另一种撤销修改的方法是执行 git revert。通过对特定的提交执行还原操作，我们会创建一个包含已还原修改的新提交。</p>
<p>假设 ec5be 添加了一个 index.js 文件。但之后我们发现其实我们再也不需要由这个提交引入的修改了。那就还原 ec5be 提交吧！</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590079.gif" alt="img"></p>
<p>完美！提交 9e78i 还原了由提交 ec5be 引入的修改。在撤销特定的提交时，git revert 非常有用，同时也不会修改分支的历史。</p>
<h2 id="拣选（Cherry-picking）"><a href="#拣选（Cherry-picking）" class="headerlink" title="拣选（Cherry-picking）"></a><strong>拣选（Cherry-picking）</strong></h2><p>当一个特定分支包含我们的活动分支需要的某个提交时，我们对那个提交执行 cherry-pick！对一个提交执行 cherry-pick 时，我们会在活动分支上创建一个新的提交，其中包含由拣选出来的提交所引入的修改。</p>
<p>假设 dev 分支上的提交 76d12 为 index.js 文件添加了一项修改，而我们希望将其整合到 master 分支中。我们并不想要整个 dev 分支，而只需要这个提交！</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590082.gif" alt="img"></p>
<p>现在 master 分支包含 76d12 引入的修改了。</p>
<h2 id="取回（Fetching）"><a href="#取回（Fetching）" class="headerlink" title="取回（Fetching）"></a><strong>取回（Fetching）</strong></h2><p>如果你有一个远程 Git 分支，比如在 GitHub 上的分支，当远程分支上包含当前分支没有的提交时，可以使用取回。比如当合并了另一个分支或你的同事推送了一个快速修复时。</p>
<p>通过在这个远程分支上执行 git fetch，我们就可在本地获取这些修改。这不会以任何方式影响你的本地分支：fetch 只是单纯地下载新的数据而已。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170589968.gif" alt="img"></p>
<p>现在我们可以看到自上次推送以来的所有修改了。这些新数据也已经在本地了，我们可以决定用这些新数据做什么了。</p>
<h2 id="拉取（Pulling）"><a href="#拉取（Pulling）" class="headerlink" title="拉取（Pulling）"></a><strong>拉取（Pulling）</strong></h2><p>尽管 git fetch 可用于获取某个分支的远程信息，但我们也可以执行 git pull。git pull 实际上是两个命令合成了一个：git fetch 和 git merge。当我们从来源拉取修改时，我们首先是像 git fetch 那样取回所有数据，然后最新的修改会自动合并到本地分支中。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170590082.gif" alt="img"></p>
<p>很好，我们现在与远程分支完美同步了，并且也有了所有最新的修改！</p>
<h2 id="Reflog"><a href="#Reflog" class="headerlink" title="Reflog"></a><strong>Reflog</strong></h2><p>每个人都会犯错，但犯错其实没啥！有时候你可能感觉你把 git repo 完全搞坏了，让你想完全删了了事。</p>
<p>git reflog 是一个非常有用的命令，可以展示已经执行过的所有动作的日志。包括合并、重置、还原，基本上包含你对你的分支所做的任何修改。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170589681.gif" alt="img"></p>
<p>如果你犯了错，你可以根据 reflog 提供的信息通过重置 HEAD 来轻松地重做！</p>
<p>假设我们实际上并不需要合并原有分支。当我们执行 git reflog 命令时，我们可以看到这个 repo 的状态在合并前位于 HEAD@{1}。那我们就执行一次 git reset，将 HEAD 重新指向在 HEAD@{1} 的位置。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601170589841.gif" alt="img"></p>
<p>我们可以看到最新的动作已被推送给 reflog。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/tips/" rel="tag">tips</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-deploy/全面解析Kafka 核心要素及其常见部署"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/deploy/%E5%85%A8%E9%9D%A2%E8%A7%A3%E6%9E%90Kafka%20%E6%A0%B8%E5%BF%83%E8%A6%81%E7%B4%A0%E5%8F%8A%E5%85%B6%E5%B8%B8%E8%A7%81%E9%83%A8%E7%BD%B2/"
    >全面解析Kafka 核心要素及其常见部署.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/deploy/%E5%85%A8%E9%9D%A2%E8%A7%A3%E6%9E%90Kafka%20%E6%A0%B8%E5%BF%83%E8%A6%81%E7%B4%A0%E5%8F%8A%E5%85%B6%E5%B8%B8%E8%A7%81%E9%83%A8%E7%BD%B2/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/deploy/">deploy</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="全面解析Kafka-核心要素及其常见部署"><a href="#全面解析Kafka-核心要素及其常见部署" class="headerlink" title="全面解析Kafka 核心要素及其常见部署"></a>全面解析Kafka 核心要素及其常见部署</h1><p>放眼当下数据为王的时代，深入了解 Apache Kafka 及其常见的部署应用，快速实现数据架构（Kafka Fast Data Architecture）已是大势所趋，刻不容缓。</p>
<p>以下分别 Kafka 架构，四大核心 API，典型应用场景，Kafka 代理与消息主题，集群的创建，流 APIs（Stream APIs）及其处理模式等不同方面展开详细介绍。</p>
<p>Kafka：分布式流平台</p>
<p>Kafka 是一个分布式流平台，用于发布和订阅消息流（也称记录流或数据流），快速有效地利用 I/O 进行数据流的批处理，压缩及解耦，并将数据流传输到数据池，应用程序和实时流分析系统中。</p>
<p>Kafka 将主题消息分区复制到多个服务器中，允许用户通过自己的应用程序来处理这些记录。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451249.webp" alt="img"></p>
<p>Kafka 四大核心 APIs</p>
<p>Kafka 由记录（records），主题（topics），使用者（consumers），生产者（producers），代理服务（brokers），日志（logs），分区（partitions）和集群（clusters）组成。</p>
<p>Kafka 主题是一个记录流，每个主题都有对应的日志，该日志是该主题在磁盘上的存储，每个主题日志又分为多个分区和片段。</p>
<p>Kafka Producer API 用于生成数据记录流。Kafka Consumer API 用于消费来自 Kafka 的记录流。</p>
<p>Broker 是在 Kafka 集群中运行的 Kafka 服务器，Kafka 集群由多个代理服务器组成。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451247.webp" alt="img"></p>
<p><strong>①生产者 API（Producer API）：</strong>消息的生产者，向 Kafka broker 发消息的客户端。</p>
<p>允许客户端与集群中运行着的 Kafka 服务器相连接，并将记录流发布到一个或多个 Kafka topics（消息主题）中。</p>
<p>一台 Kafka 服务器就是一个 broker，一个集群由多个 broker 组成，一个 broker 可以容纳多个 topic。</p>
<p><strong>②消费者 API（Consumer API）：</strong>消息消费者，向 Kafka broker 获取消息的客户端。</p>
<p>允许客户端连接集群中运行着的 Kafka 服务器，并消费其中一个或多个 Kafka topics（消息主题）的记录流。</p>
<p><strong>③流 API（Stream API）：</strong>充当流处理器，用于输入输出流的转换。</p>
<p>允许客户端充当流处理器，从一个或多个 topics（消息主题）消费输入流，并生产输出流，输出到一个或多个其他 topics（消息主题）中，从而有效地将输入流转换至输出流。</p>
<p><strong>④连接器 API（Connector API）：</strong>允许编写可重用的生产者和消费者代码。</p>
<p>我们可以从任何关系型数据库中读取数据，并将其发布到主题中，同时也可以“消费”这个主题中的数据，并将其写入关系型数据库。</p>
<p>由此可见，Connector API 支持构建和运行可重复使用的生产者或消费者，并将 topic 连接到现有的应用程序或数据系统。（例如，就关系型数据库而言，其连接器可以捕获到各个表中的每个变化。）</p>
<p>Kafka应用场景</p>
<p><strong>消息系统</strong></p>
<p>Kafka 作为企业消息传递系统，通过源系统及目标系统间的分离来实现数据交换。与 JMS 相比，Kafka 兼具高吞吐量分区及高可靠容错力的复制功能。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451096.webp" alt="img"></p>
<p><strong>Web 站点活动跟踪</strong></p>
<p>跟踪记录用户在网站上的所有事件信息，从而进行数据的分析及脱机处理。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451250.webp" alt="img"></p>
<p><strong>日志汇总</strong></p>
<p>用于处理来自不同系统的日志，尤其是那些处于微服务架构分布式环境中的系统，这类系统通常部署在不同的主机上，因此 Kafka 需要汇总来自不同系统的各类日志，进而对这些日志集中进行分析处理。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451252.webp" alt="img"></p>
<p><strong>指标收集</strong></p>
<p>Kafka 可用于收集来自各类系统/网络的指标，并进行监控，Kafka 配有专门的指标报告生成工具，如 Ganglia，Graphite 等。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451243.webp" alt="img"></p>
<p>Kafka Brokers &amp; Kafka Topics</p>
<p><strong>Kafka Broker（代理服务器）</strong></p>
<p>Kafka 集群中的一个实例称之为代理（服务器），在 Kafka 集群中，只要连接其中任意一个代理（服务器）就能访问到整个集群，每个代理在集群中通过 ID 进行标识。</p>
<p><strong>Kafka Topics（消息主题）</strong></p>
<p>一个消息主题（Topic）是一个消息记录发布后的逻辑名称，在 Kafka 中，Topic 被分为若干个分区（Partitions），用于消息的发布。</p>
<p>这些分区分布在集群的各个代理服务器（Brokers）中，为了实现可扩展性，通常将一个非常大的 Topic 分布在多个代理服务器（Broker）上。</p>
<p>由于一个 Topic 可以分为多个分区（Partition），每个分区（Partition）都是一个有序的队列。</p>
<p>分区（Partition）中的每条消息都会被分配一个有序的 ID（即偏移量，Offset）。</p>
<p>如下图所示，假设当前有一个主题（Topic），该主题（Topic）有三个分区，集群中有三个代理（Broker），则每个代理都有一个分区。要发布到分区的数据以偏移量（Offset）增量的方式追加。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451253.webp" alt="img"></p>
<p>其中“Offset”即偏移量，Kafka 的存储文件都是按照“offset.kafka”来命名，用 Offset 方式命名是为了便于查找，如果想找位于 2046 的位置，只需找到 2045.kafka 的文件即可。</p>
<p>以下是分区（Partitions）使用时值得注意的要点：</p>
<ul>
<li>每个消息主题（Topic）按名称标识，集群中允许有多个已命名的消息主题。</li>
<li>每个消息前后顺序的有效性仅限于当前分区级别（maintained at the partition level），而非跨主题。</li>
<li>数据一旦写入分区，则不会被覆盖，这就是Kafka中强调的数据不变性（immutability）</li>
<li>分区中的消息通过键（key），值（values），时间戳（timestamps）的形式一起存储，Kafka 确保每一个给定密钥的消息都会发布到同一个分区中。</li>
<li>在 Kafka 集群中，每一个分区都有一个引导程序（leader），该引导程序负责对该分区执行读/写操作。</li>
</ul>
<p><img src="http://iubest.gitee.io/pic/640-1601003451248.webp" alt="img"></p>
<p>上图是一个例子，当前集群中仅一个消息主题（Topic），该主题包含三个分区（partition0，partition1，partition2），集群中有三个代理服务器（broker1，broker2，broker3）。</p>
<p>当前每个分区的副本都复制到另外两个代理服务器（Broker）中，即每个代理服务器（Broker）上包含了三个分区。</p>
<p>因此即便其中某两个代理服务器（Broker）发生故障，也不用担心数据会丢失。</p>
<p>如上，当我们在 Kafka 中创建主题时，始终建议确保主题（Topic）的复制因子大于 1，并且小于/等于集群中的代理服务器（Broker）数量，这是非常推荐的做法。</p>
<p>上图示例中，当前主题的复制因子为 3（即，一份原始数据，两份副本数据）， 不难推算出每个分区的引导程序加上其副本数量总共为“3”。</p>
<p>该示例中，每个分区都有一个引导程序（称之为“leader”），以及其他两个同步副本（称之为“follower”）。</p>
<p>对于分区 partition 0 来说，broker1 是“leader”， broker2 和 broker3 都是“follower”，从而分区 partition 0 的所有读写操作都将在 broker1 中进行。</p>
<p>同时，之后更新的内容也会被同步复制到 broker2 和 broker3 对应的分区（partition）中。</p>
<p>创建 Kafka 集群——Demo</p>
<p>我们还是以上图中三个 Broker 组成的 Kafka 集群为例，拆解 Kafka 集群创建的步骤。</p>
<p><strong>①Kafka 集群环境准备</strong></p>
<p>首先需要准备好一台安装有 Zookeeper 的机器，没有 Zookeeper，Kafka 集群将无法工作。</p>
<p>同时建议直接从官网下载最新版本的 Apache Kafka，目前版本更新至2.11，直接解压后将其放置到 bin 目录下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https:&#x2F;&#x2F;archive.apache.org&#x2F;dist&#x2F;kafka&#x2F;1.0.0&#x2F;kafka_2.11-1.0.0.tgz</span><br></pre></td></tr></table></figure>



<p>然后启动 ZooKeeper，为什么需要 Zookeeper？它在这里主要负责协调服务，管理代理服务 Broker，确定每个分区中的引导程序，以及在 Kafka 消息主题或代理服务发生变更时及时发出警告。</p>
<p>通过以下命令可以启动一个Zookeeper实例：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451242.webp" alt="img"></p>
<p><strong>②启动 Kafka Brokers</strong></p>
<p>成功安装 Kafka 并启动 ZooKeeper 实例后，接下来就可以开启 Kafka Broker 了，这里共启动了三个 Kafka Broker。</p>
<p>具体启动方式：先定位到 Kafka 根目录下的“config”文件夹下，找到“server.properties”文件，将其复制三次。</p>
<p>然后分别命名为server_1.properties，server_2.properties 以及 server_3.properties，并针对三个文件内容做如下编辑，直接保存即可：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451186.webp" alt="img"></p>
<p>保存后通过命令开启这三个代理服务：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451252.webp" alt="img"></p>
<p><strong>③创建主题</strong></p>
<p>通过如下命令创建消息主题：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451128.webp" alt="img"></p>
<p><strong>④生成引导服务</strong></p>
<p>通过 Kafka 控制台生成器（Kafka console）指定任意一个代理服务地址，并基于之前创建的主题发布一些消息。</p>
<p>这个指定的代理服务就被视作为引导服务程序，用于访问整个集群。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451250.webp" alt="img"></p>
<p><strong>⑤“消费”消息</strong></p>
<p>通过 Kafka 控制台来使用消息，用户（即：消息消费者）需要指定任意一个代理服务（Broker）地址作为引导服务器。</p>
<p>在阅读消息时，用户（即：消息消费者）是看不到消息顺序的，上文中也提到过消息的先后顺序仅在分区级别（partition level）进行维护，而非主题级别（topic level）。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451251.webp" alt="img"></p>
<p>通过以下命令可以描述主题并查看各分区的分布情况，以及每个分区的引导服务器：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451251.webp" alt="img"></p>
<p>从上面的执行结果可以看出：</p>
<ul>
<li>broker-1 是分区 0 的引导服务器。</li>
<li>broker-2 是分区 1 的引导服务器。</li>
<li>broker-3 是分区 2 的引导服务器。</li>
<li>broker-1，broker-2，broker-3 分别具有每个分区的副本（同步且相互备份）。</li>
</ul>
<p>Kafka Streams API</p>
<p>Kafka 常被用作将流数据实时传输到其他系统中，此时 Kafka 作为中间层，主要用来解耦分离实时数据管道。</p>
<p>Kafka 流是 Kafka 生态系统的一部分，它提供了实时分析的功能，支持将流数据传输到大数据平台或 RDBMS，Cassandra，Spark 中，以进行将来的数据分析。</p>
<p>Kafka Stream API 简单易用，通过其强大的技术能力可处理所有存储于其中的数据，同时该 API 也为我们提供了一套 Kafka 标准类的实现规则。</p>
<p>在实际工作中为了能够创建支持核心业务的实时应用程序，我们需要 Kafka Stream API 的大力协助。</p>
<p>Kafka Stream API 独特之处在于，通过其构建的应用程序都是普通应用程序。</p>
<p>所以这些应用程序可以像其他任何应用程序一样，进行打包，部署和监控，而无需单独安装专门的处理集群或类似基础架构，这些额外部署的基础架构往往比较耗钱。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451242.webp" alt="img"></p>
<p>流（Stream）是 Kafka Streams 提供的最重要的抽象对象，代表了无限且持续更新的数据集。</p>
<p>流是一系列不可变数据记录的序列，具备有序，可重复，容错等特性，我们可以简单将其视为记录流（定义为：KStream）或变更日志流（定位为：KTable 或 GlobelKTable）。</p>
<p>流处理器（Stream Processor）是处理器拓扑结构中的一个节点，包含应用于流数据的处理逻辑，一系列节点组成了拓扑结构中的处理步骤（用于转换数据）。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451135.webp" alt="img"></p>
<p>Kafka Streams API 处理数据——Demo</p>
<p>Kafka Stream API 为实现流数据处理，即消息在 Kafka 中的消费及回写，提供了两种选项：</p>
<ul>
<li>高级 Kafka Streams DSL（high-level DSL）。</li>
<li>低级处理器 API：用于数据基本处理，组合处理，本地状态存储。</li>
</ul>
<p><strong>①高级 DSL（high-level DSL）</strong></p>
<p>高级 DSL 由记录流（KStream） 和日志流（KTable/GlobalKTable）两大主要抽象类别组成，包含一系列已实现的方法可供调用。</p>
<p>KStream 是记录流的抽象，其中每个数据都是无限数据集中的简单键值，KStream 提供了多种处理数据流的功能。</p>
<p>例如：map，mapValue，flatMap，flatMapValues，filter；同时还支持多个流连接，流数据的聚合。</p>
<p>KTable 是变更日志流的抽象，在变更日志中，对具有相同键的行（row）进行覆盖，因而每条数据记录都被视作为插入或更新。</p>
<p><strong>②处理器 API（lower-level processor ）</strong></p>
<p>低级处理器 API 通过扩展抽象类（AbstractProcessor），覆盖含有业务逻辑的处理方法，从而实现客户端流数据的访问，允许基于输入数据流执行相应的业务逻辑，同时将其结果作为下游数据转发至客户端。</p>
<p>相较于高级 DSL 提供具有功能样式的即用型方法，低级处理器API则按需提供处理逻辑。</p>
<p><strong>③Kafka Stream API 应用——高级 DSL Demo</strong></p>
<p>前提：必须在当前环境中有以下依赖，版本视当前情况而定。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451139.webp" alt="img"></p>
<p>导入以下包：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451133.webp" alt="img"></p>
<p>Kafka 配置属性：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451252.webp" alt="img"></p>
<p>实例化 KStreamBuilder，创建一个 KStream 对象：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451247.webp" alt="img"></p>
<p>KStreamBuilder 有个 Stream 方法，该方法以主题名称（topic name）作为参数，返回一个 KStream 对象，即，订阅了指定主题的实例化对象。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451249.webp" alt="img"></p>
<p>基于 KStream 对象，这时我们就可以使用 Kafka Streams 高级 DSL 提供的众多方法（例如：map，process，transform，join 等），然后将处理后的数据发送到另一个主题。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451248.webp" alt="img"></p>
<p>最后，通过构建器（builder）和流配置进行流式传输：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003451248.webp" alt="img"></p>
<p>通过 Kafka Streams API，我们无需单独部署集群即可在 Kafka 中进行数据流处理。</p>
<p>Kafka Streams API 给我们带来的便捷主要包含以下几个方面：</p>
<ul>
<li>高可扩展性，灵活性，分布式和容错性。</li>
<li>支持有状态和无状态处理。</li>
<li>具有窗口，联接和聚合的事件时间处理。</li>
<li>通过 Kafka Streams DSL 或较低级别的处理器 API 使用已经定义的常见转换操作。</li>
<li>对处理没有单独的群集要求（与 Kafka 集成）。</li>
<li>采用一次一个记录的处理以实现毫秒级的处理延迟。</li>
<li>支持 Kafka Connect 连接到不同的应用程序和数据库。</li>
</ul>
<p>总结</p>
<p>Kafka 的便捷操作是其备受业内人士广泛关注的原因之一，然而更重要的是其出色的稳定性，可靠性及耐用性，且具有灵活的发布/队列，可以很好地适应 N 个消费者组，具有强大的可复制性，可以为生产者提供一致性保证。</p>
<p>本次分享基于 Kafka 核心要素及其常见部署做了详情解析，希望给圈内感兴趣的人士提供技术普及，交流互补。</p>
<p><em>作者：\</em>罗小罗**</p>
<p><strong>简介：英国 TOP10 计算机专业，计算机科学与技术硕士，先后就职于汇丰，JPMorgan，HP，交行，阿里等国内外知名企业。涉及项目领域主要有：互联网金融，电商，教育，医疗等。现任就职于某世界 500 强公司，担任测试开发团队负责人，带领团队构建并持续优化自动化测试框架，研发自动化测试辅助类工具；擅长领域：单元/接口/性能/安全/自动化测试/CD/CI/DevOps；个人持续研究领域：自动化测试模型/数据分析/算法/机器学习等。</strong></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/deploy/" rel="tag">deploy</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-deploy/高可用的Redis主从复制集群，从理论到实践"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/deploy/%E9%AB%98%E5%8F%AF%E7%94%A8%E7%9A%84Redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E9%9B%86%E7%BE%A4%EF%BC%8C%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/"
    >高可用的Redis主从复制集群，从理论到实践.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/deploy/%E9%AB%98%E5%8F%AF%E7%94%A8%E7%9A%84Redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E9%9B%86%E7%BE%A4%EF%BC%8C%E4%BB%8E%E7%90%86%E8%AE%BA%E5%88%B0%E5%AE%9E%E8%B7%B5/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/deploy/">deploy</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="高可用的Redis主从复制集群，从理论到实践"><a href="#高可用的Redis主从复制集群，从理论到实践" class="headerlink" title="高可用的Redis主从复制集群，从理论到实践"></a>高可用的Redis主从复制集群，从理论到实践</h2><p><a href="javascript:void(0);">Java知音</a> <em>3天前</em></p>
<p><img src="http://iubest.gitee.io/pic/640-1601002200465.gif" alt="img"></p>
<p><em>作者**：Sicimike</em></p>
<p><em>blog.csdn.net/Baisitao_/article/details/105545410</em></p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>我们都知道，服务如果只部署一个节点，很容易出现单点故障，从而导致服务不可用。所以服务做成高可用是很有必要的，Redis服务自然也是这样。</p>
<p>本文主要从理论和实践两方面讲解Redis高可用集群。手把手教你搭建一个高可用的redis主从复制集群。</p>
<p>本文采取理论和实践穿插讲解的方式，如果只关心集群的搭建，可以跳过文中理论部分。</p>
<h2 id="前置阅读"><a href="#前置阅读" class="headerlink" title="前置阅读"></a>前置阅读</h2><ul>
<li><p>Redis持久化：</p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/Baisitao_/article/details/105461153">https://blog.csdn.net/Baisitao_/article/details/105461153</a></p>
</li>
</ul>
<h3 id="实验环境"><a href="#实验环境" class="headerlink" title="实验环境"></a>实验环境</h3><ul>
<li>VMware Workstation 15</li>
<li>CentOS Linux release 7.7.1908</li>
<li>Redis-5.0.8</li>
</ul>
<h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ul>
<li>三个节点ip分别为<code>192.168.1.101</code>、<code>192.168.1.102</code>、<code>192.168.1.103</code></li>
<li>确保三个节点都能访问互联网，并且三个节点能够相互通信</li>
<li>确保Linux的<code>yum</code>、<code>wget</code>、<code>tar</code>、<code>gcc</code>等基础命令、编译器可用</li>
<li>建议先关闭防火墙，Centos 7操作如下</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">firewall-cmd --state ## 查看防火墙状态 not running表示已经关闭</span><br><span class="line">systemctl stop firewalld.service ## 关闭防火墙</span><br><span class="line">systemctl disable firewalld.service ## 禁止开机启动防火墙</span><br></pre></td></tr></table></figure>

<h2 id="redis单机安装"><a href="#redis单机安装" class="headerlink" title="redis单机安装"></a>redis单机安装</h2><p>下载</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget http:&#x2F;&#x2F;download.redis.io&#x2F;releases&#x2F;redis-5.0.8.tar.gz</span><br></pre></td></tr></table></figure>

<p>解压</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf redis-5.0.8.tar.gz</span><br></pre></td></tr></table></figure>

<p>编译</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd redis-5.0.8</span><br><span class="line">make</span><br></pre></td></tr></table></figure>

<p>安装</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">make install  ## 或者指定安装目录 make install PREFIX&#x3D;指定路径。默认路径是&#x2F;usr&#x2F;local&#x2F;bin</span><br><span class="line">.&#x2F;utils&#x2F;install_server.sh  ## 安装成服务，如果上一步配置了PREFIX，需要把安装路径配置到环境变量&#x2F;etc&#x2F;profile</span><br></pre></td></tr></table></figure>

<p><code>install_server.sh</code>是redis提供的脚本，运行之后会让你指定几个配置：端口号、配置文件路径、日志文件路径、数据文件路径。</p>
<p>如果都设置成默认值，redis根据按照端口号来区分同一台主机上的不同的实例，因为<code>install_server.sh</code>可以多次运行，每次运行相当于安装了一个实例。</p>
<p>安装过程如果都是默认安装，会有以下几个配置：</p>
<ul>
<li>端口号：<code>6379</code></li>
<li>配置文件路径：<code>/etc/redis/6379.conf</code></li>
<li>日志文件路径：<code>/var/log/redis_6379.log</code></li>
<li>数据文件路径：<code>/var/lib/redis/6379/</code></li>
<li><code>redis-server.sh</code>路径：<code>/usr/local/bin/</code></li>
<li><code>redis-cli.sh</code>路径：<code>/usr/local/bin/</code></li>
</ul>
<p>安装成功会出现如下日志</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Copied &#x2F;tmp&#x2F;6379.conf &#x3D;&gt; &#x2F;etc&#x2F;init.d&#x2F;redis_6379</span><br><span class="line">Installing service...</span><br><span class="line">Successfully added to chkconfig!</span><br><span class="line">Successfully added to runlevels 345!</span><br><span class="line">Starting Redis server...</span><br><span class="line">Installation successful!</span><br></pre></td></tr></table></figure>

<p>可以看到redis服务已经自动启动。</p>
<h2 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h2><p>Redis主从复制是redis3.0之后自带的一种集群实现方式，不需要其他的中间件。是一种基于异步复制的主从实现方式。所以Redis主从复制并不能保证数据的强一致性.。集群在特定的条件下可能会丢失写操作。</p>
<h3 id="集群结构"><a href="#集群结构" class="headerlink" title="集群结构"></a>集群结构</h3><p>现在来搭建一个一主两从的集群，集群拓扑图如下<br><img src="http://iubest.gitee.io/pic/640-1601002200470.webp" alt="img"></p>
<p>其中<code>master</code>节点可写可读，一般用来处理写请求，<code>slave</code>节点默认情况下是只读的，所以用来处理读请求。两个<code>slave</code>节点的数据都是从<code>master</code>节点复制过去的。所以这种集群也叫读写分离。</p>
<h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>redis配置文件默认路径为<code>/etc/redis/6379.conf</code>，用vi/vim打开，三个节点都配置如下内容</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">## 需要绑定的ip地址</span><br><span class="line">bind 127.0.0.1 192.168.1.101 192.168.1.102 192.168.1.103</span><br><span class="line"></span><br><span class="line">## 关闭后台运行，便于观察</span><br><span class="line">daemonize no</span><br><span class="line"></span><br><span class="line">## 注释日志路径，让日志直接输出在控制台，便于观察</span><br><span class="line"># logfile &#x2F;var&#x2F;log&#x2F;redis_6379.log</span><br><span class="line"></span><br><span class="line">## 关闭AOF持久化模式</span><br><span class="line">appendonly no</span><br></pre></td></tr></table></figure>

<h3 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h3><p>配置完成后分别启动三个节点</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd &#x2F;usr&#x2F;local&#x2F;bin</span><br><span class="line">redis-server &#x2F;etc&#x2F;redis&#x2F;6379.conf</span><br></pre></td></tr></table></figure>

<h3 id="设置主从关系"><a href="#设置主从关系" class="headerlink" title="设置主从关系"></a>设置主从关系</h3><p>两个<code>slave</code>节点用redis-cli客户端连接redis-server后，均执行如下命令，把自己设置成<code>master</code>节点的<code>slave</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">replicaof 192.168.1.101 6379</span><br></pre></td></tr></table></figure>

<p><code>replicaof</code>也可以直接写在配置文件中（文中为了实验效果，以命令的方式执行）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">################################# REPLICATION #################################</span><br><span class="line"></span><br><span class="line"># Master-Replica replication. Use replicaof to make a Redis instance a copy of</span><br><span class="line"># another Redis server. A few things to understand ASAP about Redis replication.</span><br><span class="line">#</span><br><span class="line">#   +------------------+      +---------------+</span><br><span class="line">#   |      Master      | ---&gt; |    Replica    |</span><br><span class="line">#   | (receive writes) |      |  (exact copy) |</span><br><span class="line">#   +------------------+      +---------------+</span><br><span class="line">#</span><br><span class="line"># 1) Redis replication is asynchronous, but you can configure a master to</span><br><span class="line">#    stop accepting writes if it appears to be not connected with at least</span><br><span class="line">#    a given number of replicas.</span><br><span class="line"># 2) Redis replicas are able to perform a partial resynchronization with the</span><br><span class="line">#    master if the replication link is lost for a relatively small amount of</span><br><span class="line">#    time. You may want to configure the replication backlog size (see the next</span><br><span class="line">#    sections of this file) with a sensible value depending on your needs.</span><br><span class="line"># 3) Replication is automatic and does not need user intervention. After a</span><br><span class="line">#    network partition replicas automatically try to reconnect to masters</span><br><span class="line">#    and resynchronize with them.</span><br><span class="line">#</span><br><span class="line"># replicaof &lt;masterip&gt; &lt;masterport&gt;</span><br></pre></td></tr></table></figure>

<p><code>replicaof</code>在redis5.0之前的版本叫<code>slaveof</code>，命令描述如下</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; help slaveof</span><br><span class="line"></span><br><span class="line">  SLAVEOF host port</span><br><span class="line">  summary: Make the server a replica of another instance, or promote it as master. Deprecated starting with Redis 5. Use REPLICAOF instead.</span><br><span class="line">  since: 1.0.0</span><br><span class="line">  group: server</span><br><span class="line"></span><br><span class="line">127.0.0.1:6379&gt; help replicaof</span><br><span class="line"></span><br><span class="line">  REPLICAOF host port</span><br><span class="line">  summary: Make the server a replica of another instance, or promote it as master.</span><br><span class="line">  since: 5.0.0</span><br><span class="line">  group: server</span><br></pre></td></tr></table></figure>

<p>命令成功执行后<code>192.168.1.101（master）</code>会出现如下日志</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">1817:M 16 Apr 2020 22:33:36.802 * Replica 192.168.1.102:6379 asks for synchronization</span><br><span class="line">1817:M 16 Apr 2020 22:33:36.802 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for &#39;e801c600a0a2381a65e1aec22daba7db82cb02f8&#39;, my replication IDs are &#39;be75572b8e6624da4971aa16448600c9822fd42a&#39; and &#39;0000000000000000000000000000000000000000&#39;)</span><br><span class="line">1817:M 16 Apr 2020 22:33:36.803 * Starting BGSAVE for SYNC with target: disk</span><br><span class="line">1817:M 16 Apr 2020 22:33:36.837 * Background saving started by pid 1822</span><br><span class="line">1822:C 16 Apr 2020 22:33:36.944 * DB saved on disk</span><br><span class="line">1822:C 16 Apr 2020 22:33:36.944 * RDB: 6 MB of memory used by copy-on-write</span><br><span class="line">1817:M 16 Apr 2020 22:33:37.038 * Background saving terminated with success</span><br><span class="line">1817:M 16 Apr 2020 22:33:37.038 * Synchronization with replica 192.168.1.102:6379 succeeded</span><br></pre></td></tr></table></figure>

<p>我们逐行看一下<code>192.168.1.101（master）</code>做了哪些事。</p>
<ul>
<li>第一行意思是有一个<code>salve</code>节点<code>192.168.1.102:6379</code>请求同步</li>
<li>第二行意思是会进行全量同步，因为是第一次请求同步</li>
<li>第三行意思是开始执行<code>BGSAVE</code>把数据持久化到磁盘</li>
<li>第四行意思是pid为1822子进程开始执行持久化</li>
<li>第五行意思是持久化完成</li>
<li>第六行意思是<code>copy-on-write</code>机制使用了6M内存</li>
</ul>
<p>最后两行表示同步过程已经完成。<code>master</code>节点是把数据以RDB的形式持久化到磁盘，再通过网络发送给<code>slave</code>。参数<code>repl-diskless-sync</code>设置成<code>no</code>的话，表示数据不经过磁盘，直接发送给<code>slave</code>。</p>
<p>看了<code>192.168.1.101（master）</code>的日志，再来看<code>salve</code>的日志，任取一个<code>slave</code>的日志</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">2013:S 16 Apr 2020 22:33:36.233 * Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.233 * REPLICAOF 192.168.1.101:6379 enabled (user request from &#39;id&#x3D;3 addr&#x3D;127.0.0.1:33550 fd&#x3D;8 name&#x3D; age&#x3D;4 idle&#x3D;0 flags&#x3D;N db&#x3D;0 sub&#x3D;0 psub&#x3D;0 multi&#x3D;-1 qbuf&#x3D;49 qbuf-free&#x3D;32719 obl&#x3D;0 oll&#x3D;0 omem&#x3D;0 events&#x3D;r cmd&#x3D;replicaof&#39;)</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.808 * Connecting to MASTER 192.168.1.101:6379</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.808 * MASTER &lt;-&gt; REPLICA sync started</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.809 * Non blocking connect for SYNC fired the event.</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.810 * Master replied to PING, replication can continue...</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.811 * Trying a partial resynchronization (request e801c600a0a2381a65e1aec22daba7db82cb02f8:1).</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.946 * Full resync from master: a9861cdcfdb3358ea0a3bb5a4df2895938c1c2d0:0</span><br><span class="line">2013:S 16 Apr 2020 22:33:36.946 * Discarding previously cached master state.</span><br><span class="line">2013:S 16 Apr 2020 22:33:37.048 * MASTER &lt;-&gt; REPLICA sync: receiving 175 bytes from master</span><br><span class="line">2013:S 16 Apr 2020 22:33:37.048 * MASTER &lt;-&gt; REPLICA sync: Flushing old data</span><br><span class="line">2013:S 16 Apr 2020 22:33:37.048 * MASTER &lt;-&gt; REPLICA sync: Loading DB in memory</span><br><span class="line">2013:S 16 Apr 2020 22:33:37.048 * MASTER &lt;-&gt; REPLICA sync: Finished with success</span><br></pre></td></tr></table></figure>

<p><code>salve</code>节点日志较多，告诉我们具体做了这些事</p>
<ul>
<li>向<code>192.168.1.101:6379(master)</code>请求同步</li>
<li>发送指令<code>SYNC</code></li>
<li>收到<code>master</code>的回复</li>
<li>全量同步，收到了175 bytes</li>
<li>清空自身的数据(<code>Flushing old data</code>)</li>
<li>加载<code>master</code>传送的数据到内存(<code>Loading DB in memory</code>)</li>
</ul>
<p>结合<code>master</code>和<code>slave</code>日志，可以看出复制的大致过程。</p>
<p>完整的主从复制的过程如下</p>
<p><img src="http://iubest.gitee.io/pic/640-1601002200466.webp" alt="img">redis主从复制</p>
<p><code>master</code>收到某个<code>slave</code>第一次请求的同步时，会进行全量同步，在同步期间会把执行过的修改数据的命令写入缓存，等同步完成后，再发送给<code>slave</code>节点执行。第一次全量同步完成后，<code>master</code>会持续给<code>slave</code>节点发送写命令，以保证主从节点数据一致性。</p>
<p>在这里可以思考一个问题，<code>slave</code>节点在全量同步的这段时间中，里面的数据能不能被客户端查询呢？</p>
<p><code>replicaof-server-stale-data</code>参数设置成<code>yes</code>表示可以查，设置成<code>no</code>表示同步必须完成才能查。</p>
<h3 id="操作"><a href="#操作" class="headerlink" title="操作"></a>操作</h3><p>先往<code>master</code>节点写入数据</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.101:6379&gt; set key1 hello</span><br><span class="line">OK</span><br></pre></td></tr></table></figure>

<p>再从<code>slave</code>节点获取（注意提示符中的ip地址），毫无疑问是可以获取的</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.102:6379&gt; get key1</span><br><span class="line">&quot;hello&quot;</span><br></pre></td></tr></table></figure>

<p>如果往<code>slave</code>节点写入数据会怎样？</p>
<p>默认情况下slave节点禁止写入，所以会报错。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.102:6379&gt; set key2 world</span><br><span class="line">(error) READONLY You can&#39;t write against a read only replica.</span><br></pre></td></tr></table></figure>

<p><code>replica-read-only</code>参数可以设置<code>slave</code>允许写入</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"># You can configure a replica instance to accept writes or not. Writing against</span><br><span class="line"># a replica instance may be useful to store some ephemeral data (because data</span><br><span class="line"># written on a replica will be easily deleted after resync with the master) but</span><br><span class="line"># may also cause problems if clients are writing to it because of a</span><br><span class="line"># misconfiguration.</span><br><span class="line">#</span><br><span class="line"># Since Redis 2.6 by default replicas are read-only.</span><br><span class="line">#</span><br><span class="line"># Note: read only replicas are not designed to be exposed to untrusted clients</span><br><span class="line"># on the internet. It&#39;s just a protection layer against misuse of the instance.</span><br><span class="line"># Still a read only replica exports by default all the administrative commands</span><br><span class="line"># such as CONFIG, DEBUG, and so forth. To a limited extent you can improve</span><br><span class="line"># security of read only replicas using &#39;rename-command&#39; to shadow all the</span><br><span class="line"># administrative &#x2F; dangerous commands.</span><br><span class="line">replica-read-only yes</span><br></pre></td></tr></table></figure>

<p>至此，最简单的主从复制集群已经搭建完成。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601002200536.webp" alt="img"></p>
<h2 id="故障"><a href="#故障" class="headerlink" title="故障"></a>故障</h2><p>你已经是一个成熟的程序员了，应该要学会面向故障编程。</p>
<p>在这个集群中有三个节点，两种角色。<code>salve</code>可能会挂，<code>master</code>也可能会挂。我们先看下<code>salve</code>节点挂了会怎样。</p>
<h3 id="slave故障"><a href="#slave故障" class="headerlink" title="slave故障"></a>slave故障</h3><p>首先让一台<code>slave</code>宕机，由于配置了2个<code>slave</code>节点，所以一个出了故障，不至于整个服务不可用。只要尽快处理故障，恢复<code>slave</code>即可，实验步骤如下。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601002200538.webp" alt="img"></p>
<p>现在重启出故障的<code>slave</code>节点</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;usr&#x2F;local&#x2F;bin&#x2F;redis-server &#x2F;etc&#x2F;redis&#x2F;6379.conf --replicaof 192.168.1.101 6379</span><br></pre></td></tr></table></figure>

<p>观察<code>master</code>，会打印如下日志信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">2168:M 17 Apr 2020 13:38:16.282 * Replica 192.168.1.102:6379 asks for synchronization</span><br><span class="line">2168:M 17 Apr 2020 13:38:16.282 * Partial resynchronization request from 192.168.1.102:6379 accepted. Sending 143 bytes of backlog starting from offset 1473.</span><br></pre></td></tr></table></figure>

<p>可以看到只打印了2行日志。表示收到了<code>192.168.1.102:6379（slave）</code>节点的同步请求，并且接受同步，从偏移（<code>offset</code>）1473开始传输，共传输了143 bytes。这意味着<code>slave</code>的重新连接，并没有触发全量同步，而是增量同步。同步的数据只是故障期间在<code>master</code>写入的那部分数据。</p>
<p>上面的操作是没有开启<code>AOF</code>的情况，如果开启<code>AOF</code>，情况又不一样。下面来操作开启<code>AOF</code>的情况，操作步骤和上面一样，区别仅仅是<code>slave</code>节点重启时开启<code>AOF</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;usr&#x2F;local&#x2F;bin&#x2F;redis-server &#x2F;etc&#x2F;redis&#x2F;6379.conf --replicaof 192.168.1.101 6379 --appendonly yes</span><br></pre></td></tr></table></figure>

<p>观察<code>master</code>节点，可以看到如下日志</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">2168:M 17 Apr 2020 13:45:21.977 * Replica 192.168.1.102:6379 asks for synchronization</span><br><span class="line">2168:M 17 Apr 2020 13:45:21.977 * Full resync requested by replica 192.168.1.102:6379</span><br><span class="line">2168:M 17 Apr 2020 13:45:21.977 * Starting BGSAVE for SYNC with target: disk</span><br><span class="line">2168:M 17 Apr 2020 13:45:21.978 * Background saving started by pid 2306</span><br><span class="line">2306:C 17 Apr 2020 13:45:22.009 * DB saved on disk</span><br><span class="line">2306:C 17 Apr 2020 13:45:22.010 * RDB: 8 MB of memory used by copy-on-write</span><br><span class="line">2168:M 17 Apr 2020 13:45:22.111 * Background saving terminated with success</span><br><span class="line">2168:M 17 Apr 2020 13:45:22.111 * Synchronization with replica 192.168.1.102:6379 succeeded</span><br></pre></td></tr></table></figure>

<p>根据日志可以看出，<code>slave</code>节点重启时如果开启了<code>AOF</code>，会触发全量同步。即使整个实验一开始就把所以节点都开启<code>AOF</code>，这里也会触发全量同步。</p>
<p>下面是<code>slave</code>日志，也可以证明触发了全量同步。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">2598:S 17 Apr 2020 13:45:21.967 * Ready to accept connections</span><br><span class="line">2598:S 17 Apr 2020 13:45:21.968 * Connecting to MASTER 192.168.1.101:6379</span><br><span class="line">2598:S 17 Apr 2020 13:45:21.968 * MASTER &lt;-&gt; REPLICA sync started</span><br><span class="line">2598:S 17 Apr 2020 13:45:21.969 * Non blocking connect for SYNC fired the event.</span><br><span class="line">2598:S 17 Apr 2020 13:45:21.971 * Master replied to PING, replication can continue...</span><br><span class="line">2598:S 17 Apr 2020 13:45:21.973 * Partial resynchronization not possible (no cached master)</span><br><span class="line">2598:S 17 Apr 2020 13:45:21.977 * Full resync from master: 8b57ea32e3bada6e91d3f371123cb693df2eec8b:2235</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.107 * MASTER &lt;-&gt; REPLICA sync: receiving 271 bytes from master</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.108 * MASTER &lt;-&gt; REPLICA sync: Flushing old data</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.122 * MASTER &lt;-&gt; REPLICA sync: Loading DB in memory</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.122 * MASTER &lt;-&gt; REPLICA sync: Finished with success</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.125 * Background append only file rewriting started by pid 2602</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.178 * AOF rewrite child asks to stop sending diffs.</span><br><span class="line">2602:C 17 Apr 2020 13:45:22.179 * Parent agreed to stop sending diffs. Finalizing AOF...</span><br><span class="line">2602:C 17 Apr 2020 13:45:22.179 * Concatenating 0.00 MB of AOF diff received from parent.</span><br><span class="line">2602:C 17 Apr 2020 13:45:22.179 * SYNC append only file rewrite performed</span><br><span class="line">2602:C 17 Apr 2020 13:45:22.180 * AOF rewrite: 4 MB of memory used by copy-on-write</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.274 * Background AOF rewrite terminated with success</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.274 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)</span><br><span class="line">2598:S 17 Apr 2020 13:45:22.275 * Background AOF rewrite finished successfully</span><br></pre></td></tr></table></figure>

<h3 id="master故障"><a href="#master故障" class="headerlink" title="master故障"></a>master故障</h3><p>由于在这个集群中，<code>master</code>节点只有一个，万一宕机了，整个服务就无法写入数据了，相当于服务不可用。这个时候救世主就出现了。哦，不，是哨兵（Sentinel）出现了。</p>
<p>Redis Sentinel（哨兵）是Redis官方的高可用性解决方案，用于管理多个 Redis 服务器（instance），哨兵的作用主要有三个：</p>
<ul>
<li>监控（Monitoring）：Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。</li>
<li>提醒（Notification）：当被监控的某个 Redis 服务器出现问题时， Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。</li>
<li>自动故障迁移（Automatic failover）：当一个主服务器不能正常工作时，Sentinel 会开始一次自动故障迁移操作， 它会将失效主服务器（<code>master</code>）的其中一个从服务器（<code>slave</code>）升级为新的主服务器（<code>master</code>），并让失效主服务器的其他从服务器改为复制新的主服务器；当客户端试图连接失效的主服务器时，集群也会向客户端返回新主服务器的地址， 使得集群可以使用新主服务器代替失效服务器。</li>
</ul>
<p>如果单单只是一个哨兵实例来监控集群，那哨兵必定也存在单点故障的问题，所以需要多个哨兵实例。加入哨兵后的集群结构如下</p>
<p><img src="http://iubest.gitee.io/pic/640-1601002200307.webp" alt="img">redis集群</p>
<p>26379是<code>sentinel</code>的默认端口，三个哨兵分别放在三个节点上。</p>
<h3 id="哨兵"><a href="#哨兵" class="headerlink" title="哨兵"></a>哨兵</h3><p>redis安装包的解压目录下会有一个<code>sentinel.conf</code>文件，这就是哨兵的配置文件，为了方便，把它拷贝到和redis配置文件相同的目录</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">## 拷贝哨兵配置文件</span><br><span class="line">cp sentinel.conf &#x2F;etc&#x2F;redis&#x2F;</span><br><span class="line">## 配置哨兵的配置文件</span><br><span class="line">vim &#x2F;etc&#x2F;redis&#x2F;sentinel.conf</span><br></pre></td></tr></table></figure>

<p>需要改的地方只有一个，就是指定哨兵要监控哪个<code>master</code>，因为<code>master</code>是可以知道有哪些<code>slave</code>节点连接了自己，所以监控<code>master</code>就够了。注意三个<code>sentinel</code>节点都是配置<code>master</code>的ip和端口</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"># sentinel monitor &lt;master-name&gt; &lt;ip&gt; &lt;redis-port&gt; &lt;quorum&gt;</span><br><span class="line">#</span><br><span class="line"># Tells Sentinel to monitor this master, and to consider it in O_DOWN</span><br><span class="line"># (Objectively Down) state only if at least &lt;quorum&gt; sentinels agree.</span><br><span class="line">#</span><br><span class="line"># Note that whatever is the ODOWN quorum, a Sentinel will require to</span><br><span class="line"># be elected by the majority of the known Sentinels in order to</span><br><span class="line"># start a failover, so no failover can be performed in minority.</span><br><span class="line">#</span><br><span class="line"># Replicas are auto-discovered, so you don&#39;t need to specify replicas in</span><br><span class="line"># any way. Sentinel itself will rewrite this configuration file adding</span><br><span class="line"># the replicas using additional configuration options.</span><br><span class="line"># Also note that the configuration file is rewritten when a</span><br><span class="line"># replica is promoted to master.</span><br><span class="line">#</span><br><span class="line"># Note: master name should not include special characters or spaces.</span><br><span class="line"># The valid charset is A-z 0-9 and the three characters &quot;.-_&quot;.</span><br><span class="line">sentinel monitor mymaster 192.168.1.101 6379 2</span><br></pre></td></tr></table></figure>

<p>配置指示 Sentinel 去监视一个名为<code>mymaster</code>的主服务器， 这个主服务器的 IP 地址为 192.168.1.101 ， 端口号为 6379 。后面那个2表示这个主服务器判断为失效至少需要 2 个 Sentinel 同意 （只要同意 Sentinel 的数量不达标，自动故障迁移就不会执行）。</p>
<p>搜索Java知音公众号，回复“后端面试”，送你一份Java面试题宝典.pdf</p>
<blockquote>
<p>不过要注意， 无论设置要多少个 Sentinel 同意才能判断一个服务器失效， 一个 Sentinel 都需要获得系统中多数（majority） Sentinel 的支持， 才能发起一次自动故障迁移</p>
</blockquote>
<p>正是为了更好的区分多数和少数，所以一般使用奇数个sentinel实例来监控集群。</p>
<p>配置文件修改完成后，开始启动三个哨兵，哨兵的启动有两种方式：直接运行<code>redis-sentinel</code>、运行<code>redis-server --sentinel</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-server &#x2F;etc&#x2F;redis&#x2F;sentinel.conf --sentinel</span><br></pre></td></tr></table></figure>

<p>第一个哨兵启动日志如下</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">2873:X 17 Apr 2020 20:56:54.495 # WARNING: The TCP backlog setting of 511 cannot be enforced because &#x2F;proc&#x2F;sys&#x2F;net&#x2F;core&#x2F;somaxconn is set to the lower value of 128.</span><br><span class="line">2873:X 17 Apr 2020 20:56:54.498 # Sentinel ID is 643817dcf5ba6d53a737782a75706a62df869e33</span><br><span class="line">2873:X 17 Apr 2020 20:56:54.498 # +monitor master mymaster 192.168.1.101 6379 quorum 2</span><br><span class="line">2873:X 17 Apr 2020 20:56:54.500 * +slave slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2873:X 17 Apr 2020 20:56:54.503 * +slave slave 192.168.1.103:6379 192.168.1.103 6379 @ mymaster 192.168.1.101 6379</span><br></pre></td></tr></table></figure>

<p>可以看到哨兵打印出了自己的ID，还监控了<code>192.168.1.101 6379（master）</code>和两个<code>slave</code>节点</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">3031:X 17 Apr 2020 20:59:59.153 # WARNING: The TCP backlog setting of 511 cannot be enforced because &#x2F;proc&#x2F;sys&#x2F;net&#x2F;core&#x2F;somaxconn is set to the lower value of 128.</span><br><span class="line">3031:X 17 Apr 2020 20:59:59.158 # Sentinel ID is e784d728f7a813de688ea800a88bda6aca0512ff</span><br><span class="line">3031:X 17 Apr 2020 20:59:59.158 # +monitor master mymaster 192.168.1.101 6379 quorum 2</span><br><span class="line">3031:X 17 Apr 2020 20:59:59.164 * +slave slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">3031:X 17 Apr 2020 20:59:59.166 * +slave slave 192.168.1.103:6379 192.168.1.103 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">3031:X 17 Apr 2020 21:00:00.115 * +sentinel sentinel 643817dcf5ba6d53a737782a75706a62df869e33 192.168.1.101 26379 @ mymaster 192.168.1.101 6379</span><br></pre></td></tr></table></figure>

<p>启动第二个哨兵时，也打印了同样的日志。除此之外，还多打印了一行关于sentinel的日志。可以看出打印出的sentinel的ID就是第一个哨兵的。也就是说哨兵在监控<code>master</code>的时候，除了可以发下<code>slave</code>节点，还可以发现监控<code>master</code>节点的其他哨兵。回头再看第一个哨兵的日志，也会多打印一行，就是第二个哨兵的ID。</p>
<p>三个哨兵已经准备就绪，接下来再让<code>master</code>宕机。</p>
<p><code>master</code>宕机30秒后，Sentinel 认为服务器已经宕机，由参数<code>sentinel down-after-milliseconds</code>指定</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># sentinel down-after-milliseconds &lt;master-name&gt; &lt;milliseconds&gt;</span><br><span class="line">#</span><br><span class="line"># Number of milliseconds the master (or any attached replica or sentinel) should</span><br><span class="line"># be unreachable (as in, not acceptable reply to PING, continuously, for the</span><br><span class="line"># specified period) in order to consider it in S_DOWN state (Subjectively</span><br><span class="line"># Down).</span><br><span class="line">#</span><br><span class="line"># Default is 30 seconds.</span><br></pre></td></tr></table></figure>

<p>超过半数的Sentinel感知到<code>master</code>宕机后会进行投票选举，从剩下的两个<code>slave</code>中选出一个<code>master</code>。三个哨兵日志分别如下</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">2873:X 17 Apr 2020 21:02:57.687 # +sdown master mymaster 192.168.1.101 6379</span><br><span class="line">2873:X 17 Apr 2020 21:02:57.765 # +new-epoch 1</span><br><span class="line">2873:X 17 Apr 2020 21:02:57.766 # +vote-for-leader a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 1</span><br><span class="line">2873:X 17 Apr 2020 21:02:58.326 # +config-update-from sentinel a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 192.168.1.103 26379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2873:X 17 Apr 2020 21:02:58.326 # +switch-master mymaster 192.168.1.101 6379 192.168.1.103 6379</span><br><span class="line">2873:X 17 Apr 2020 21:02:58.327 * +slave slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">2873:X 17 Apr 2020 21:02:58.327 * +slave slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">2873:X 17 Apr 2020 21:03:28.343 # +sdown slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">3031:X 17 Apr 2020 21:02:57.686 # +sdown master mymaster 192.168.1.101 6379</span><br><span class="line">3031:X 17 Apr 2020 21:02:57.743 # +new-epoch 1</span><br><span class="line">3031:X 17 Apr 2020 21:02:57.745 # +vote-for-leader a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 1</span><br><span class="line">3031:X 17 Apr 2020 21:02:57.776 # +odown master mymaster 192.168.1.101 6379 #quorum 3&#x2F;2</span><br><span class="line">3031:X 17 Apr 2020 21:02:57.776 # Next failover delay: I will not start a failover before Fri Apr 17 21:08:57 2020</span><br><span class="line">3031:X 17 Apr 2020 21:02:58.308 # +config-update-from sentinel a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 192.168.1.103 26379 @ mymaster 192.168.1.101 6379</span><br><span class="line">3031:X 17 Apr 2020 21:02:58.308 # +switch-master mymaster 192.168.1.101 6379 192.168.1.103 6379</span><br><span class="line">3031:X 17 Apr 2020 21:02:58.309 * +slave slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">3031:X 17 Apr 2020 21:02:58.309 * +slave slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">3031:X 17 Apr 2020 21:03:28.352 # +sdown slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.690 # +sdown master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.749 # +odown master mymaster 192.168.1.101 6379 #quorum 2&#x2F;2</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.749 # +new-epoch 1</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.749 # +try-failover master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.750 # +vote-for-leader a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 1</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.759 # 643817dcf5ba6d53a737782a75706a62df869e33 voted for a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 1</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.759 # e784d728f7a813de688ea800a88bda6aca0512ff voted for a32bc56146695d9ebcbceaff2b0b8a5339c61a5b 1</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.841 # +elected-leader master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.841 # +failover-state-select-slave master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.924 # +selected-slave slave 192.168.1.103:6379 192.168.1.103 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:57.925 * +failover-state-send-slaveof-noone slave 192.168.1.103:6379 192.168.1.103 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:58.001 * +failover-state-wait-promotion slave 192.168.1.103:6379 192.168.1.103 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:58.266 # +promoted-slave slave 192.168.1.103:6379 192.168.1.103 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:58.266 # +failover-state-reconf-slaves master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:58.317 * +slave-reconf-sent slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:58.817 # -odown master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:59.292 * +slave-reconf-inprog slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:59.292 * +slave-reconf-done slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:59.347 # +failover-end master mymaster 192.168.1.101 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:59.347 # +switch-master mymaster 192.168.1.101 6379 192.168.1.103 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:59.347 * +slave slave 192.168.1.102:6379 192.168.1.102 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">2833:X 17 Apr 2020 21:02:59.347 * +slave slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 6379</span><br><span class="line">2833:X 17 Apr 2020 21:03:29.355 # +sdown slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 6379</span><br></pre></td></tr></table></figure>

<p>从日志可以看到大致的过程</p>
<ul>
<li>三个sentinel都发下<code>master</code>宕机了，把它的状态设置成<code>odown</code></li>
<li>开启一轮投票，选出了新的<code>master</code>为<code>192.168.1.103:6379</code></li>
<li>sentinel更新配置文件</li>
<li><code>192.168.1.103:6379</code>成为新的<code>master</code>，故障转移完成</li>
</ul>
<p>从最后几行日志可以看出，现在的<code>master</code>是<code>192.168.1.103 6379</code>，而<code>slave</code>是<code>192.168.1.102:6379</code>和<code>192.168.1.101:6379</code>，并且<code>192.168.1.101:6379</code>是<code>sdown</code>状态的<code>slave</code>。</p>
<p>Redis 的 Sentinel 中关于下线（<code>down</code>）有两个不同的概念：</p>
<ul>
<li>主观下线（Subjectively Down， 简称 <code>SDOWN</code>）指的是单个 Sentinel 实例对服务器做出的下线判断。</li>
<li>客观下线（Objectively Down， 简称 <code>ODOWN</code>）指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断， 并且通过 <code>SENTINEL is-master-down-by-addr</code> 命令互相交流之后， 得出的服务器下线判断。（一个 Sentinel 可以通过向另一个 Sentinel 发送 <code>SENTINEL is-master-down-by-addr</code> 命令来询问对方是否认为给定的服务器已下线。）</li>
</ul>
<p>查看哨兵配置文件，发现哨兵监控的节点已经成新的<code>master</code>了</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@localhost redis-5.0.8]&gt; cat &#x2F;etc&#x2F;redis&#x2F;sentinel.conf |grep &quot;sentinel monitor mymaster&quot;</span><br><span class="line">sentinel monitor mymaster 192.168.1.103 6379 2</span><br></pre></td></tr></table></figure>

<p>之前的<code>master</code>出故障了，但是现在故障修复了，准备重启。重新启动原来的<code>192.168.1.101（master）</code>，它会心甘情愿的成为<code>slave</code>，还是抢回<code>master</code>地位呢？</p>
<p>欲知后事如何，请听下回分解。<br>看下哨兵日志就知道，哨兵会打印如下日志</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">3031:X 17 Apr 2020 21:05:32.297 * +convert-to-slave slave 192.168.1.101:6379 192.168.1.101 6379 @ mymaster 192.168.1.103 63791</span><br></pre></td></tr></table></figure>

<p>就是把<code>192.168.1.101:6379</code>变成可用的<code>slave</code>，所以即使原来的<code>master</code>重启了，也不会去抢回<code>master</code>地位。</p>
<p>至此，基于哨兵的高可用redis集群才算搭建完成。</p>
<p>搜索Java知音公众号，回复“后端面试”，送你一份Java面试题宝典.pdf</p>
<h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p>这里再把主从复制相关的理论总结一下。<code>slave</code>节点第一次追随<code>master</code>的时候，会发送<code>sync</code>请求同步。请求同步在Redis2.8之后由<code>psync [runId] [offset]</code>命令完成，<code>psync</code>命令既支持全量复制，也支持增量复制。Redis4.0之后，<code>psync</code>再一次进行了优化。</p>
<ul>
<li><code>runId</code>：是每个redis节点启动都会生成的唯一标识，每次redis重启后，runId也会发生变化</li>
<li><code>offset</code>：是复制的偏移量，<code>master</code>和<code>slave</code>都会记录自己和对方的复制偏移量，如果不一致，表示需要继续同步</li>
</ul>
<p>除此之外<code>master</code>节点还会维护一个缓冲队列（<code>replication backlog buffer</code>，复制积压缓冲区默认大小1M，参数<code>repl-backing-size</code>设置），当<code>slave</code>正在复制<code>master</code>时，如果出现网络异常导致命令丢失时。<code>slave</code>会向<code>master</code>要求重新发送丢失的命令数据，如果<code>master</code>的复制积压缓冲区内存将这部分数据则直接发送给<code>slave</code>，这样就可以保持主从节点复制的一致性。</p>
<p>然而redis2.8版本的<code>psync</code>还有两个问题无法解决：redis重启时触发全量复制、故障切换之后，<code>slave</code>追随新的<code>master</code>触发全量同步。</p>
<p>这两个问题在redis4.0版本的<code>psync</code>得到了解决。主要通过两个复制id（<code>master_replid</code>和<code>master_replid2</code>）来实现</p>
<p>这些信息都可以通过<code>info replication</code>命令来查询<br>这是<code>master</code>节点的信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.103:6379&gt; info replication</span><br><span class="line"># Replication</span><br><span class="line">role:master</span><br><span class="line">connected_slaves:2</span><br><span class="line">slave0:ip&#x3D;192.168.1.101,port&#x3D;6379,state&#x3D;online,offset&#x3D;98,lag&#x3D;0</span><br><span class="line">slave1:ip&#x3D;192.168.1.102,port&#x3D;6379,state&#x3D;online,offset&#x3D;98,lag&#x3D;0</span><br><span class="line">master_replid:8b1d6db7a9e63c0360ffed0ec6d3a51199f08f2b</span><br><span class="line">master_replid2:0000000000000000000000000000000000000000</span><br><span class="line">master_repl_offset:98</span><br><span class="line">second_repl_offset:-1</span><br><span class="line">repl_backlog_active:1</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:1</span><br><span class="line">repl_backlog_histlen:98</span><br></pre></td></tr></table></figure>

<p>这是<code>slave</code>节点的信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.101:6379&gt; info replication</span><br><span class="line"># Replication</span><br><span class="line">role:slave</span><br><span class="line">master_host:192.168.1.103</span><br><span class="line">master_port:6379</span><br><span class="line">master_link_status:up</span><br><span class="line">master_last_io_seconds_ago:3</span><br><span class="line">master_sync_in_progress:0</span><br><span class="line">slave_repl_offset:5334</span><br><span class="line">slave_priority:100</span><br><span class="line">slave_read_only:1</span><br><span class="line">connected_slaves:0</span><br><span class="line">master_replid:8b1d6db7a9e63c0360ffed0ec6d3a51199f08f2b</span><br><span class="line">master_replid2:0000000000000000000000000000000000000000</span><br><span class="line">master_repl_offset:5334</span><br><span class="line">second_repl_offset:-1</span><br><span class="line">repl_backlog_active:1</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:1</span><br><span class="line">repl_backlog_histlen:5334</span><br></pre></td></tr></table></figure>

<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文以穿插的方式讲解了redis主从复制的实践和部分原理，可能会导致看起来略显凌乱。之所以采用穿插的方式，是为了让读者把理论和实践关联起来，形成一个完整的知识体系，而不仅仅是零碎的知识点。</p>
<p>只关心实验的旁友可以先跳过文中理论部分，并不会影响实验效果。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a target="_blank" rel="noopener" href="http://redis.cn/topics/sentinel.html">http://redis.cn/topics/sentinel.html</a></li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/deploy/" rel="tag">deploy</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-develop/Python采集linux服务器数据在Django Web界面展示"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/develop/Python%E9%87%87%E9%9B%86linux%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E6%8D%AE%E5%9C%A8Django%20Web%E7%95%8C%E9%9D%A2%E5%B1%95%E7%A4%BA/"
    >Python采集linux服务器数据在Django Web界面展示.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/Python%E9%87%87%E9%9B%86linux%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E6%8D%AE%E5%9C%A8Django%20Web%E7%95%8C%E9%9D%A2%E5%B1%95%E7%A4%BA/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Python采集linux服务器数据在Django-Web界面展示"><a href="#Python采集linux服务器数据在Django-Web界面展示" class="headerlink" title="Python采集linux服务器数据在Django Web界面展示"></a>Python采集linux服务器数据在Django Web界面展示</h1><h3 id="Django显示服务器性能监控"><a href="#Django显示服务器性能监控" class="headerlink" title="Django显示服务器性能监控"></a>Django显示服务器性能监控</h3><ul>
<li><ul>
<li><p>监控系统CPU、内存和进程信息</p>
</li>
<li><ul>
<li>一、运行环境与项目目录</li>
<li>二、配置文档：Setting</li>
<li>二、创建 Models，操作数据</li>
<li>三、创建数据库</li>
<li>四、 views.py</li>
<li>五、创建html模版</li>
<li>六、urls.py添加路由</li>
<li>七、运行程序，显示结果</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="监控系统CPU、内存和进程信息"><a href="#监控系统CPU、内存和进程信息" class="headerlink" title="监控系统CPU、内存和进程信息"></a>监控系统CPU、内存和进程信息</h2><h3 id="一、运行环境与项目目录"><a href="#一、运行环境与项目目录" class="headerlink" title="一、运行环境与项目目录"></a>一、运行环境与项目目录</h3><ol>
<li>Django 2.0.7</li>
<li>python 3.7</li>
<li>pymysql 0.9.3</li>
</ol>
<p><img src="http://iubest.gitee.io/pic/640-1601016028012.webp" alt="img"></p>
<h3 id="二、配置文档：Setting"><a href="#二、配置文档：Setting" class="headerlink" title="二、配置文档：Setting"></a>二、配置文档：Setting</h3><p>1.mysql 数据库</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"># Author: Allan</span><br><span class="line"># Datetime: 2019-05-21</span><br><span class="line">import pymysql</span><br><span class="line">pymysql.install_as_MySQLdb()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">DATABASES &#x3D; &#123;</span><br><span class="line">    &#39;default&#39;: &#123;</span><br><span class="line">        # 注释sqlite3</span><br><span class="line">        # &#39;ENGINE&#39;: &#39;django.db.backends.sqlite3&#39;,</span><br><span class="line">        # &#39;NAME&#39;: os.path.join(BASE_DIR, &#39;db.sqlite3&#39;),</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        # mysql</span><br><span class="line">        &#39;ENGINE&#39;: &#39;django.db.backends.mysql&#39;,</span><br><span class="line">        &#39;NAME&#39;: &#39;database&#39;,</span><br><span class="line">        &#39;USER&#39;: &#39;username&#39;,</span><br><span class="line">        &#39;PASSWORD&#39;: &#39;password&#39;,</span><br><span class="line">        &#39;HOST&#39;: &#39;ip&#39;,</span><br><span class="line">        &#39;PORT&#39;: 3306,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>2.Application definition</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS &#x3D; [</span><br><span class="line">   &#39;django.contrib.admin&#39;,</span><br><span class="line">   &#39;django.contrib.auth&#39;,</span><br><span class="line">   &#39;django.contrib.contenttypes&#39;,</span><br><span class="line">   &#39;django.contrib.sessions&#39;,</span><br><span class="line">   &#39;django.contrib.messages&#39;,</span><br><span class="line">   &#39;django.contrib.staticfiles&#39;,</span><br><span class="line">   &#39;web&#39;  #添加App，下面python3 mkmigrations web使用</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">MIDDLEWARE &#x3D; [</span><br><span class="line">   &#39;django.middleware.security.SecurityMiddleware&#39;,</span><br><span class="line">   &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;,</span><br><span class="line">   &#39;django.middleware.common.CommonMiddleware&#39;,</span><br><span class="line">   # &#39;django.middleware.csrf.CsrfViewMiddleware&#39;, 注释这一行，防止403错误</span><br><span class="line">   &#39;django.contrib.auth.middleware.AuthenticationMiddleware&#39;,</span><br><span class="line">   &#39;django.contrib.messages.middleware.MessageMiddleware&#39;,</span><br><span class="line">   &#39;django.middleware.clickjacking.XFrameOptionsMiddleware&#39;,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">ROOT_URLCONF &#x3D; &#39;monitor.urls&#39;</span><br><span class="line"></span><br><span class="line">TEMPLATES &#x3D; [</span><br><span class="line">   &#123;</span><br><span class="line">       &#39;BACKEND&#39;: &#39;django.template.backends.django.DjangoTemplates&#39;,</span><br><span class="line">       &#39;DIRS&#39;: [os.path.join(BASE_DIR, &#39;templates&#39;)],  #修改模版目录</span><br><span class="line">       &#39;APP_DIRS&#39;: True,</span><br><span class="line">       &#39;OPTIONS&#39;: &#123;</span><br><span class="line">           &#39;context_processors&#39;: [</span><br><span class="line">               &#39;django.template.context_processors.debug&#39;,</span><br><span class="line">               &#39;django.template.context_processors.request&#39;,</span><br><span class="line">               &#39;django.contrib.auth.context_processors.auth&#39;,</span><br><span class="line">               &#39;django.contrib.messages.context_processors.messages&#39;,</span><br><span class="line">           ],</span><br><span class="line">       &#125;,</span><br><span class="line">   &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>3.Security</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># SECURITY WARNING: don&#39;t run with debug turned on in production!</span><br><span class="line">DEBUG &#x3D; True</span><br><span class="line"></span><br><span class="line">ALLOWED_HOSTS &#x3D; [&#39;*&#39;]</span><br></pre></td></tr></table></figure>

<p>4.Internationalization</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">LANGUAGE_CODE &#x3D; &#39;en-us&#39;</span><br><span class="line"># 修改时区</span><br><span class="line">TIME_ZONE &#x3D; &#39;Asia&#x2F;Shanghai&#39;</span><br><span class="line"></span><br><span class="line">USE_I18N &#x3D; True</span><br><span class="line"></span><br><span class="line">USE_L10N &#x3D; True</span><br><span class="line"># 修改时区</span><br><span class="line">USE_TZ &#x3D; False</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="二、创建-Models，操作数据"><a href="#二、创建-Models，操作数据" class="headerlink" title="二、创建 Models，操作数据"></a>二、创建 Models，操作数据</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">from django.db import models</span><br><span class="line"></span><br><span class="line"># Create your models here.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class CpuInfo(models.Model):</span><br><span class="line">    # CPU信息</span><br><span class="line">    time &#x3D; models.DateTimeField()</span><br><span class="line">    host &#x3D; models.CharField(max_length&#x3D;40)</span><br><span class="line">    usage_system &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line">    usage_user &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line">    usage_softirq &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line">    usage_iowait &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class MemoryInfo(models.Model):</span><br><span class="line">    #内存信息</span><br><span class="line">    time &#x3D; models.DateTimeField()</span><br><span class="line">    host &#x3D; models.CharField(max_length&#x3D;40)</span><br><span class="line">    used_percent &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class ProcstatInfo(models.Model):</span><br><span class="line">    # 进程信息</span><br><span class="line">    time &#x3D; models.DateTimeField()</span><br><span class="line">    host &#x3D; models.CharField(max_length&#x3D;100)</span><br><span class="line">    exe &#x3D; models.CharField(max_length&#x3D;40)</span><br><span class="line">    pid &#x3D; models.FloatField()</span><br><span class="line">    cpu_usage &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line">    memory_rss &#x3D; models.FloatField(null&#x3D;True)</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="三、创建数据库"><a href="#三、创建数据库" class="headerlink" title="三、创建数据库"></a>三、创建数据库</h3><p>1.Terminal 下运行manager.py</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"># python3 manager.py mkmigrations web</span><br><span class="line"># python3 manager.py migrate</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>2.查询mysql</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">mysql&gt; show tables;</span><br><span class="line">ERROR 2006 (HY000): MySQL server has gone away</span><br><span class="line">No connection. Trying to reconnect...</span><br><span class="line">Connection id:    449745</span><br><span class="line">Current database: perf_monitor</span><br><span class="line"></span><br><span class="line">+----------------------------+</span><br><span class="line">| Tables_in_perf_monitor     |</span><br><span class="line">+----------------------------+</span><br><span class="line">| auth_group                 |</span><br><span class="line">| auth_group_permissions     |</span><br><span class="line">| auth_permission            |</span><br><span class="line">| auth_user                  |</span><br><span class="line">| auth_user_groups           |</span><br><span class="line">| auth_user_user_permissions |</span><br><span class="line">| django_admin_log           |</span><br><span class="line">| django_content_type        |</span><br><span class="line">| django_migrations          |</span><br><span class="line">| django_session             |</span><br><span class="line">| web_cpuinfo                |</span><br><span class="line">| web_memoryinfo             |</span><br><span class="line">| web_procstatinfo           |</span><br><span class="line">+----------------------------+</span><br><span class="line">13 rows in set (0.51 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; </span><br></pre></td></tr></table></figure>

<p><strong>四、</strong> views.py</p>
<p>创建两个views，一个显示CPU和内存信息；另一个现实进程信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"># Author: Allan</span><br><span class="line"># Datetime: 2019-05-21</span><br><span class="line">import time</span><br><span class="line">import json</span><br><span class="line">from django.shortcuts import render</span><br><span class="line">from web import models</span><br><span class="line"></span><br><span class="line"># Create your views here.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def show_procstat(request):</span><br><span class="line">    servers &#x3D; [&#39;rotestZone01&#39;, &#39;rotestZone02&#39;]</span><br><span class="line">    procstat_ret &#x3D; &#123;&#125;</span><br><span class="line">    for server in servers:</span><br><span class="line">        procstat_info &#x3D; models.ProcstatInfo.objects.filter(host&#x3D;server)</span><br><span class="line">        procstat_exe &#x3D; &#123;&#125;</span><br><span class="line">        for row in procstat_info:</span><br><span class="line">            temp_time &#x3D; int(time.mktime(row.time.timetuple())) * 1000</span><br><span class="line">            key &#x3D; str(row.exe) + &#39;_&#39; + str(int(row.pid))</span><br><span class="line">            if key not in procstat_exe:</span><br><span class="line">                procstat_exe[key] &#x3D; &#123;&#125;</span><br><span class="line">                procstat_exe[key][&#39;cpu_usage&#39;] &#x3D; []</span><br><span class="line">                procstat_exe[key][&#39;memory_rss&#39;] &#x3D; []</span><br><span class="line">            procstat_exe[key][&#39;cpu_usage&#39;].append([temp_time, row.cpu_usage])</span><br><span class="line">            procstat_exe[key][&#39;memory_rss&#39;].append([temp_time, row.memory_rss])</span><br><span class="line">        procstat_ret[server] &#x3D; procstat_exe</span><br><span class="line"></span><br><span class="line">    return render(request, &#39;show_procstat.html&#39;, &#123;&quot;procstat_ret&quot;: json.dumps(procstat_ret)&#125;)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def show_servers(request):</span><br><span class="line">    servers &#x3D; [&#39;rotestZone01&#39;, &#39;rotestZone02&#39;]</span><br><span class="line">    server_res &#x3D; &#123;&#125;</span><br><span class="line">    cpu_res &#x3D; &#123;&#125;</span><br><span class="line">    mem_res &#x3D; &#123;&#125;</span><br><span class="line">    for server in servers:</span><br><span class="line">        cpu_info &#x3D; models.CpuInfo.objects.filter(host&#x3D;server)</span><br><span class="line">        cpu_ret &#x3D; &#123;&#125;</span><br><span class="line">        usage_system &#x3D; []</span><br><span class="line">        usage_user &#x3D; []</span><br><span class="line">        usage_softirq &#x3D; []</span><br><span class="line">        usage_iowait &#x3D; []</span><br><span class="line">        for row in cpu_info:</span><br><span class="line">            temp_time &#x3D; int(time.mktime(row.time.timetuple())) * 1000</span><br><span class="line">            usage_system.append([temp_time, row.usage_system])</span><br><span class="line">            usage_user.append([temp_time, row.usage_user])</span><br><span class="line">            usage_softirq.append([temp_time, row.usage_softirq])</span><br><span class="line">            usage_iowait.append([temp_time, row.usage_iowait])</span><br><span class="line">        cpu_ret[&#39;usage_system&#39;] &#x3D; usage_system</span><br><span class="line">        cpu_ret[&#39;usage_user&#39;] &#x3D; usage_user</span><br><span class="line">        cpu_ret[&#39;usage_softirq&#39;] &#x3D; usage_softirq</span><br><span class="line">        cpu_ret[&#39;usage_iowait&#39;] &#x3D; usage_iowait</span><br><span class="line">        cpu_res[server] &#x3D; cpu_ret</span><br><span class="line"></span><br><span class="line">        mem_info &#x3D; models.MemoryInfo.objects.filter(host&#x3D;server)</span><br><span class="line">        mem_ret &#x3D; []</span><br><span class="line">        for row in mem_info:</span><br><span class="line">            temp_time &#x3D; int(time.mktime(row.time.timetuple()))*1000</span><br><span class="line">            mem_ret.append([temp_time, row.used_percent])</span><br><span class="line">        mem_res[server] &#x3D; mem_ret</span><br><span class="line"></span><br><span class="line">    server_res[&#39;cpu_res&#39;] &#x3D; cpu_res</span><br><span class="line">    server_res[&#39;mem_res&#39;] &#x3D; mem_res</span><br><span class="line"></span><br><span class="line">    return render(request, &#39;show_servers.html&#39;, &#123;&quot;server_res&quot;: json.dumps(server_res)&#125;)</span><br></pre></td></tr></table></figure>

<p><strong>五、创建html模版</strong></p>
<p>创建两个html模版，一个显示CPU和内存信息；另一个现实进程信息</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">&lt;html xmlns&#x3D;&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;1999&#x2F;html&quot;&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">&lt;meta charset&#x3D;&quot;UTF-8&quot; &#x2F;&gt;</span><br><span class="line">&lt;title&gt;服务器监控信息&lt;&#x2F;title&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.jquery.com&#x2F;jquery-3.1.1.min.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.highcharts.com&#x2F;highcharts.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.highcharts.com&#x2F;modules&#x2F;exporting.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.highcharts.com&#x2F;modules&#x2F;export-data.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script language&#x3D;&quot;JavaScript&quot;&gt;</span><br><span class="line">$(document).ready(function() &#123;</span><br><span class="line">    $(&quot;#servers&quot;).change(function ()&#123;</span><br><span class="line">        var selected &#x3D; $(this).children(&#39;option:selected&#39;).val();</span><br><span class="line">        var server_res &#x3D; jQuery.parseJSON(&#39;&#123;&#123; server_res|safe &#125;&#125;&#39;);</span><br><span class="line"></span><br><span class="line">        var cpu_res &#x3D; server_res[&#39;cpu_res&#39;]</span><br><span class="line">        var cpu_info &#x3D; cpu_res[selected]</span><br><span class="line"></span><br><span class="line">        Highcharts.setOptions(&#123;</span><br><span class="line">            global:&#123;</span><br><span class="line">                useUTC: false</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        Highcharts.chart(&#39;cpu&#39;, &#123;</span><br><span class="line">            chart: &#123;</span><br><span class="line">                zoomType: &#39;x&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            title: &#123;</span><br><span class="line">                text: selected + &#39;: CPU 信息&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            subtitle: &#123;</span><br><span class="line">                text: document.ontouchstart &#x3D;&#x3D;&#x3D; undefined ?</span><br><span class="line">                    &#39;Click and drag in the plot area to zoom in&#39; : &#39;Pinch the chart to zoom in&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            xAxis: &#123;</span><br><span class="line">                type: &#39;datetime&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            yAxis: &#123;</span><br><span class="line">                title: &#123;</span><br><span class="line">                    text: &#39;使用率 (%)&#39;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            legend: &#123;</span><br><span class="line">                enabled: false</span><br><span class="line">            &#125;,</span><br><span class="line">            plotOptions: &#123;</span><br><span class="line">                area: &#123;</span><br><span class="line">                    fillColor: &#123;</span><br><span class="line">                        linearGradient: &#123;</span><br><span class="line">                            x1: 0,</span><br><span class="line">                            y1: 0,</span><br><span class="line">                            x2: 0,</span><br><span class="line">                            y2: 1</span><br><span class="line">                        &#125;,</span><br><span class="line">                        stops: [</span><br><span class="line">                            [0, Highcharts.getOptions().colors[0]],</span><br><span class="line">                            [1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get(&#39;rgba&#39;)]</span><br><span class="line">                        ]</span><br><span class="line">                    &#125;,</span><br><span class="line">                    marker: &#123;</span><br><span class="line">                        radius: 2</span><br><span class="line">                    &#125;,</span><br><span class="line">                    lineWidth: 1,</span><br><span class="line">                    states: &#123;</span><br><span class="line">                        hover: &#123;</span><br><span class="line">                            lineWidth: 1</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;,</span><br><span class="line">                    threshold: null</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line"></span><br><span class="line">            series: [</span><br><span class="line">                &#123;</span><br><span class="line">                   type: &#39;area&#39;,</span><br><span class="line">                   name: &#39;usage_system&#39;,</span><br><span class="line">                   data: cpu_info[&#39;usage_system&#39;]</span><br><span class="line">                &#125;,</span><br><span class="line">                &#123;</span><br><span class="line">                   type: &#39;area&#39;,</span><br><span class="line">                   name: &#39;usage_user&#39;,</span><br><span class="line">                   data: cpu_info[&#39;usage_user&#39;]</span><br><span class="line">                &#125;,</span><br><span class="line">                &#123;</span><br><span class="line">                   type: &#39;area&#39;,</span><br><span class="line">                   name: &#39;usage_softirq&#39;,</span><br><span class="line">                   data: cpu_info[&#39;usage_softirq&#39;]</span><br><span class="line">                &#125;,</span><br><span class="line">                &#123;</span><br><span class="line">                   type: &#39;area&#39;,</span><br><span class="line">                   name: &#39;usage_iowait&#39;,</span><br><span class="line">                   data: cpu_info[&#39;usage_iowait&#39;]</span><br><span class="line">                &#125;]</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        var mem_res &#x3D; server_res[&#39;mem_res&#39;]</span><br><span class="line">        var mem_info &#x3D; mem_res[selected]</span><br><span class="line">        Highcharts.chart(&#39;mem&#39;, &#123;</span><br><span class="line">            chart: &#123;</span><br><span class="line">                zoomType: &#39;x&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            title: &#123;</span><br><span class="line">                text: selected + &#39;: 内存信息&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            subtitle: &#123;</span><br><span class="line">                text: document.ontouchstart &#x3D;&#x3D;&#x3D; undefined ?</span><br><span class="line">                    &#39;Click and drag in the plot area to zoom in&#39; : &#39;Pinch the chart to zoom in&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            xAxis: &#123;</span><br><span class="line">                type: &#39;datetime&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            yAxis: &#123;</span><br><span class="line">                title: &#123;</span><br><span class="line">                    text: &#39;使用率 (%)&#39;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            legend: &#123;</span><br><span class="line">                enabled: false</span><br><span class="line">            &#125;,</span><br><span class="line">            plotOptions: &#123;</span><br><span class="line">                area: &#123;</span><br><span class="line">                    fillColor: &#123;</span><br><span class="line">                        linearGradient: &#123;</span><br><span class="line">                            x1: 0,</span><br><span class="line">                            y1: 0,</span><br><span class="line">                            x2: 0,</span><br><span class="line">                            y2: 1</span><br><span class="line">                        &#125;,</span><br><span class="line">                        stops: [</span><br><span class="line">                            [0, Highcharts.getOptions().colors[0]],</span><br><span class="line">                            [1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get(&#39;rgba&#39;)]</span><br><span class="line">                        ]</span><br><span class="line">                    &#125;,</span><br><span class="line">                    marker: &#123;</span><br><span class="line">                        radius: 2</span><br><span class="line">                    &#125;,</span><br><span class="line">                    lineWidth: 1,</span><br><span class="line">                    states: &#123;</span><br><span class="line">                        hover: &#123;</span><br><span class="line">                            lineWidth: 1</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;,</span><br><span class="line">                    threshold: null</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line"></span><br><span class="line">            series: [&#123;</span><br><span class="line">                type: &#39;area&#39;,</span><br><span class="line">                name: &#39;used_percent&#39;,</span><br><span class="line">                data: mem_info</span><br><span class="line">            &#125;]</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">&lt;&#x2F;script&gt;</span><br><span class="line">&lt;&#x2F;head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;div&gt;&lt;H2&gt;服务器监控信息&lt;&#x2F;H2&gt;&lt;&#x2F;div&gt;</span><br><span class="line">&lt;select id&#x3D;&quot;servers&quot;&gt;</span><br><span class="line">    &lt;option value&#x3D;&quot;servers&quot;&gt;请选择要监控的服务器&lt;&#x2F;option&gt;</span><br><span class="line">    &lt;option value&#x3D;&quot;rotestZone01&quot;&gt;rotestZone01&lt;&#x2F;option&gt;</span><br><span class="line">    &lt;option value&#x3D;&quot;rotestZone02&quot;&gt;rotestZone02&lt;&#x2F;option&gt;</span><br><span class="line">&lt;&#x2F;select&gt;</span><br><span class="line"></span><br><span class="line">&lt;div id&#x3D;&quot;cpu&quot; style&#x3D;&quot;width: 1560px; height: 400px; margin: 0 auto&quot;&gt;&lt;&#x2F;div&gt;</span><br><span class="line">&lt;div id&#x3D;&quot;mem&quot; style&#x3D;&quot;width: 1560px; height: 400px; margin: 0 auto&quot;&gt;&lt;&#x2F;div&gt;</span><br><span class="line">&lt;&#x2F;body&gt;</span><br><span class="line">&lt;&#x2F;html&gt;</span><br></pre></td></tr></table></figure>



<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br></pre></td><td class="code"><pre><span class="line">&lt;html xmlns&#x3D;&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;1999&#x2F;html&quot; xmlns&#x3D;&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;1999&#x2F;html&quot;&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">&lt;meta charset&#x3D;&quot;UTF-8&quot; &#x2F;&gt;</span><br><span class="line">&lt;title&gt;服务器监控信息&lt;&#x2F;title&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.jquery.com&#x2F;jquery-3.1.1.min.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.highcharts.com&#x2F;highcharts.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.highcharts.com&#x2F;modules&#x2F;exporting.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script src&#x3D;&quot;https:&#x2F;&#x2F;code.highcharts.com&#x2F;modules&#x2F;export-data.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;script language&#x3D;&quot;JavaScript&quot;&gt;</span><br><span class="line">$(document).ready(function() &#123;</span><br><span class="line">    $(&quot;#servers&quot;).change(function ()&#123;</span><br><span class="line">        var selected &#x3D; $(this).children(&#39;option:selected&#39;).val();</span><br><span class="line">        var server_res &#x3D; jQuery.parseJSON(&#39;&#123;&#123; procstat_ret|safe &#125;&#125;&#39;);</span><br><span class="line">        var xServers &#x3D; server_res[selected]</span><br><span class="line"></span><br><span class="line">        var obj&#x3D;document.getElementById(&#39;processes&#39;);</span><br><span class="line">        if (obj.options.length !&#x3D; 1)&#123;</span><br><span class="line">            obj.options.length&#x3D;1;</span><br><span class="line">        &#125;</span><br><span class="line">        for(var key in xServers)</span><br><span class="line">        &#123;</span><br><span class="line">            obj.options.add(new Option(key,key));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        $(&quot;#processes&quot;).change(function ()&#123;</span><br><span class="line">            var procSelected &#x3D; $(this).children(&#39;option:selected&#39;).val();</span><br><span class="line">            var dataMem &#x3D; new Array();</span><br><span class="line">            var dataCpu &#x3D; new Array();</span><br><span class="line">            for(var key in xServers)</span><br><span class="line">                &#123;</span><br><span class="line">                    if (key &#x3D;&#x3D; procSelected)&#123;</span><br><span class="line">                        dataCpu &#x3D; xServers[key][&#39;cpu_usage&#39;]</span><br><span class="line">                        dataMem &#x3D; xServers[key][&#39;memory_rss&#39;];</span><br><span class="line">                        break;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            Highcharts.chart(&#39;cpu&#39;, &#123;</span><br><span class="line">            chart: &#123;</span><br><span class="line">                zoomType: &#39;x&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            title: &#123;</span><br><span class="line">                text: selected + &#39;: CPU 信息&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            subtitle: &#123;</span><br><span class="line">                text: document.ontouchstart &#x3D;&#x3D;&#x3D; undefined ?</span><br><span class="line">                    &#39;Click and drag in the plot area to zoom in&#39; : &#39;Pinch the chart to zoom in&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            xAxis: &#123;</span><br><span class="line">                type: &#39;datetime&#39;</span><br><span class="line">            &#125;,</span><br><span class="line">            yAxis: &#123;</span><br><span class="line">                title: &#123;</span><br><span class="line">                    text: &#39;使用率 (%)&#39;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            legend: &#123;</span><br><span class="line">                enabled: false</span><br><span class="line">            &#125;,</span><br><span class="line">            plotOptions: &#123;</span><br><span class="line">                area: &#123;</span><br><span class="line">                    fillColor: &#123;</span><br><span class="line">                        linearGradient: &#123;</span><br><span class="line">                            x1: 0,</span><br><span class="line">                            y1: 0,</span><br><span class="line">                            x2: 0,</span><br><span class="line">                            y2: 1</span><br><span class="line">                        &#125;,</span><br><span class="line">                        stops: [</span><br><span class="line">                            [0, Highcharts.getOptions().colors[0]],</span><br><span class="line">                            [1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get(&#39;rgba&#39;)]</span><br><span class="line">                        ]</span><br><span class="line">                    &#125;,</span><br><span class="line">                    marker: &#123;</span><br><span class="line">                        radius: 2</span><br><span class="line">                    &#125;,</span><br><span class="line">                    lineWidth: 1,</span><br><span class="line">                    states: &#123;</span><br><span class="line">                        hover: &#123;</span><br><span class="line">                            lineWidth: 1</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;,</span><br><span class="line">                    threshold: null</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line"></span><br><span class="line">            series: [</span><br><span class="line">                &#123;</span><br><span class="line">                   type: &#39;area&#39;,</span><br><span class="line">                   name: &#39;cpu_usage&#39;,</span><br><span class="line">                   data: dataCpu</span><br><span class="line">                &#125;]</span><br><span class="line">            &#125;);</span><br><span class="line"></span><br><span class="line">            Highcharts.chart(&#39;mem&#39;, &#123;</span><br><span class="line">                chart: &#123;</span><br><span class="line">                    zoomType: &#39;x&#39;</span><br><span class="line">                &#125;,</span><br><span class="line">                title: &#123;</span><br><span class="line">                    text: selected + &#39;: 内存信息&#39;</span><br><span class="line">                &#125;,</span><br><span class="line">                subtitle: &#123;</span><br><span class="line">                    text: document.ontouchstart &#x3D;&#x3D;&#x3D; undefined ?</span><br><span class="line">                        &#39;Click and drag in the plot area to zoom in&#39; : &#39;Pinch the chart to zoom in&#39;</span><br><span class="line">                &#125;,</span><br><span class="line">                xAxis: &#123;</span><br><span class="line">                    type: &#39;datetime&#39;</span><br><span class="line">                &#125;,</span><br><span class="line">                yAxis: &#123;</span><br><span class="line">                    title: &#123;</span><br><span class="line">                        text: &#39;使用率 (%)&#39;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;,</span><br><span class="line">                legend: &#123;</span><br><span class="line">                    enabled: false</span><br><span class="line">                &#125;,</span><br><span class="line">                plotOptions: &#123;</span><br><span class="line">                    area: &#123;</span><br><span class="line">                        fillColor: &#123;</span><br><span class="line">                            linearGradient: &#123;</span><br><span class="line">                                x1: 0,</span><br><span class="line">                                y1: 0,</span><br><span class="line">                                x2: 0,</span><br><span class="line">                                y2: 1</span><br><span class="line">                            &#125;,</span><br><span class="line">                            stops: [</span><br><span class="line">                                [0, Highcharts.getOptions().colors[0]],</span><br><span class="line">                                [1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get(&#39;rgba&#39;)]</span><br><span class="line">                            ]</span><br><span class="line">                        &#125;,</span><br><span class="line">                        marker: &#123;</span><br><span class="line">                            radius: 2</span><br><span class="line">                        &#125;,</span><br><span class="line">                        lineWidth: 1,</span><br><span class="line">                        states: &#123;</span><br><span class="line">                            hover: &#123;</span><br><span class="line">                                lineWidth: 1</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;,</span><br><span class="line">                        threshold: null</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;,</span><br><span class="line"></span><br><span class="line">                series: [&#123;</span><br><span class="line">                    type: &#39;area&#39;,</span><br><span class="line">                    name: &#39;memory_rss&#39;,</span><br><span class="line">                    data: dataMem</span><br><span class="line">                &#125;]</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">&lt;&#x2F;script&gt;</span><br><span class="line">&lt;&#x2F;head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;div&gt;&lt;H2&gt;服务器监控信息&lt;&#x2F;H2&gt;&lt;&#x2F;div&gt;</span><br><span class="line"></span><br><span class="line">&lt;div id&#x3D;&quot;monitor&quot;&gt;</span><br><span class="line">    &lt;select id&#x3D;&quot;servers&quot;&gt;</span><br><span class="line">        &lt;option value&#x3D;&quot;server&quot;&gt;请选择要监控的服务器&lt;&#x2F;option&gt;</span><br><span class="line">        &lt;option value&#x3D;&quot;rotestZone01&quot;&gt;rotestZone01&lt;&#x2F;option&gt;</span><br><span class="line">        &lt;option value&#x3D;&quot;rotestZone02&quot;&gt;rotestZone02&lt;&#x2F;option&gt;</span><br><span class="line">    &lt;&#x2F;select&gt;</span><br><span class="line">    &lt;select id&#x3D;&quot;processes&quot;&gt;</span><br><span class="line">        &lt;option value&#x3D;&quot;process&quot;&gt;请选择要监控的进程&lt;&#x2F;option&gt;</span><br><span class="line">    &lt;&#x2F;select&gt;</span><br><span class="line">&lt;&#x2F;div&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&lt;div id&#x3D;&quot;cpu&quot; style&#x3D;&quot;width: 1560px; height: 400px; margin: 0 auto&quot;&gt;&lt;&#x2F;div&gt;</span><br><span class="line">&lt;div id&#x3D;&quot;mem&quot; style&#x3D;&quot;width: 1560px; height: 400px; margin: 0 auto&quot;&gt;&lt;&#x2F;div&gt;</span><br><span class="line">&lt;&#x2F;body&gt;</span><br><span class="line">&lt;&#x2F;html&gt;</span><br></pre></td></tr></table></figure>

<h3 id="六、urls-py添加路由"><a href="#六、urls-py添加路由" class="headerlink" title="六、urls.py添加路由"></a>六、urls.py添加路由</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># Author: Allan</span><br><span class="line"># Datetime: 2019-05-21</span><br><span class="line">from django.contrib import admin</span><br><span class="line">from django.urls import path</span><br><span class="line">from web import views</span><br><span class="line"></span><br><span class="line">urlpatterns &#x3D; [</span><br><span class="line">    # path(&#39;admin&#x2F;&#39;, admin.site.urls),</span><br><span class="line">    path(&#39;show_servers&#39;, views.show_servers),</span><br><span class="line">    path(&#39;show_procstat&#39;, views.show_procstat),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h3 id="七、运行程序，显示结果"><a href="#七、运行程序，显示结果" class="headerlink" title="七、运行程序，显示结果"></a>七、运行程序，显示结果</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># python3 manager.py runserver</span><br></pre></td></tr></table></figure>

<p>CPU和内存信息</p>
<p><img src="http://iubest.gitee.io/pic/640-1601016028011.webp" alt="img"></p>
<p>进程信息</p>
<p><img src="http://iubest.gitee.io/pic/640-1601016027914.webp" alt="img"></p>
<p>往期精彩文章</p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247484708&idx=1&sn=2a7230ade0ce0bd9059e92752e03ba18&chksm=fb638a31cc14032772d778f1a8fa5dc73f9aba6c99546c9ab25d9f91f706be3cff34572f0c1a&scene=21#wechat_redirect">kubernetes全栈技术+企业案例演示【带你快速掌握和使用k8s】</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247486272&idx=1&sn=e993bc259db899b79922863859d24bb2&chksm=fb638455cc140d43dd091b09efc0c744b82f32d905c78ce9be71aae8c6d324d625f110fa1093&scene=21#wechat_redirect">突破运维和开发瓶颈、Python、k8s、DevOps转型一网打尽！</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247486031&idx=1&sn=58003addbf36f2858f7d0dfbf887bb2a&chksm=fb63855acc140c4cf0346cc3433bc874e7cd37a5b17cf5254147a1b20b538cf70158f2234b0f&scene=21#wechat_redirect">python运维开发实战-基础篇
</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247486201&idx=1&sn=0d45a2660a7480d73cf72ba67fe52f3a&chksm=fb6385eccc140cfa90bef6759c768c94ac2ca904c827fb2dcd969858ba46b818145e76e09c62&scene=21#wechat_redirect">python运维和开发实战-高级篇</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247486210&idx=1&sn=078e172a34c22e5db1f57b17c21b2efe&chksm=fb638417cc140d011a058d7cf83795bf75cfc327acbb3727c968058ff2807874a26f87843080&scene=21#wechat_redirect">python运维和开发实战-安装和创建Django项目</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247484220&idx=1&sn=72ae437836108fbecbeb86752aad888c&chksm=fb638c29cc14053fafcb62be244f5a279052b8d159c9c530e73495cfd8ab97205988780d4aaa&scene=21#wechat_redirect">谈谈我的IT发展之路</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247484445&idx=1&sn=bf8928e7aae815b8e7f9ac56a67289ce&chksm=fb638b08cc14021e5e025f2f0b89d18ec5ef19f9dec4429a56f356e8b596d01095e313dab3e3&scene=21#wechat_redirect">Prometheus+Grafana+Alertmanager搭建全方位的监控告警系统-超详细文档</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU0NjEwMTg4Mg==&mid=2247484264&idx=1&sn=4d74de0cb117c07ba7c73ea21c42bf94&chksm=fb638c7dcc14056b9f0261e05e4d36a057c4511fb5806222f71b7965c3573fed86672b2c93b7&scene=21#wechat_redirect">k8s1.18多master节点高可用集群安装-超详细中文官方文档</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzIzOTQ3NDg1Mw==&mid=2247483668&idx=1&sn=a7d0768b7eb034ef2e2f23e25b1b1845&chksm=e928c0dede5f49c878898e04329f08d8adf554397476283aaa3f2554f71a8bf20986755fbcc9&scene=21#wechat_redirect">linux面试题汇总</a></p>
<p>END</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-develop/Spring+Quartz定时任务"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/develop/Spring+Quartz%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/"
    >Spring+Quartz定时任务.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/Spring+Quartz%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p>在程序中常常有定时任务的需求，例如每隔一周生成一次报表、每个月月末清空用户积分等等。Spring也提供了相应的支持，我们可以非常方便的按时执行任务。</p>
<hr>
<h2 id="项目准备"><a href="#项目准备" class="headerlink" title="项目准备"></a><strong>项目准备</strong></h2><p>**<br>**</p>
<p>这里我使用Gradle来建立项目，然后在build.gradle中添加下面一行。springVersion的值是目前最新的Spring版本’4.3.7.RELEASE’。使用Maven的话也添加相应的行。spring-context会自动引入spring-core等几个最基本的依赖。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compile group: &#39;org.springframework&#39;, name: &#39;spring-context&#39;, version: springVersion</span><br></pre></td></tr></table></figure>



<p>定时任务属于Spring的核心支持部分，所以我们不需要再添加其他的依赖了。所以定时任务功能既可以在命令行程序中使用，也可以在Java Web程序中使用。当然后者可能使用的更广泛一些（毕竟Web程序需要一直运行的嘛）。</p>
<p>这里我们定义两个任务，后面会让它们可以定时执行。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">public interface IService &#123;</span><br><span class="line">    void doService();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class SimpleService implements IService &#123;</span><br><span class="line">    @Override</span><br><span class="line">    public void doService() &#123;</span><br><span class="line">        LocalTime time &#x3D; LocalTime.now();</span><br><span class="line">        System.out.println(&quot;This is a simple service:&quot; + time);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public class ExpensiveTaskService implements IService &#123;</span><br><span class="line">    @Override</span><br><span class="line">    public void doService() &#123;</span><br><span class="line">        try &#123;</span><br><span class="line">            Thread.sleep(TimeUnit.SECONDS.toMillis(1));</span><br><span class="line">            LocalTime time &#x3D; LocalTime.now();</span><br><span class="line">            System.out.println(&quot;This is an expensive task:&quot; + time);</span><br><span class="line">        &#125; catch (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<hr>
<h2 id="Spring的任务抽象"><a href="#Spring的任务抽象" class="headerlink" title="Spring的任务抽象"></a><strong>Spring的任务抽象</strong></h2><p>**<br>**</p>
<h3 id="TaskExecutor"><a href="#TaskExecutor" class="headerlink" title="TaskExecutor"></a><strong>TaskExecutor</strong></h3><p>**<br>**</p>
<p>TaskExecutor接口是任务执行接口，类似于java.util.concurrent.Executor ，该接口只有一个方法execute(Runnable task)，用于执行任务。</p>
<p>Spring提供了一组TaskExecutor的实现，详细列表可以看这里34.2.1. TaskExecutor types。要使用它们也很简单，直接注册为Spring Bean，然后注入到程序中即可使用。</p>
<h3 id="TaskScheduler"><a href="#TaskScheduler" class="headerlink" title="TaskScheduler"></a><strong>TaskScheduler</strong></h3><p>**<br>**</p>
<p>TaskScheduler接口是定时器的抽象，它的源代码如下。可以看到，该接口包含了一组方法用于指定任务执行的时间。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">public interface TaskScheduler &#123;</span><br><span class="line"></span><br><span class="line">    ScheduledFuture schedule(Runnable task, Trigger trigger);</span><br><span class="line"></span><br><span class="line">    ScheduledFuture schedule(Runnable task, Date startTime);</span><br><span class="line"></span><br><span class="line">    ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);</span><br><span class="line"></span><br><span class="line">    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);</span><br><span class="line"></span><br><span class="line">    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);</span><br><span class="line"></span><br><span class="line">    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>Spring提供了两个实现，一是TimerManagerTaskScheduler，会将任务代理到CommonJ TimerManager实例。第二个是ThreadPoolTaskScheduler，当我们不需要管理线程的时候就可以使用该类。而且它还同时实现了TaskExecutor接口，所以一个ThreadPoolTaskScheduler实例即可同时用于执行定时任务。</p>
<h3 id="Trigger"><a href="#Trigger" class="headerlink" title="Trigger"></a><strong>Trigger</strong></h3><p>**<br>**</p>
<p>在定时器接口的方法中我们可以发现一个方法接受Trigger接口， 而Trigger也是一个接口，抽象了触发任务执行的触发器。</p>
<p>Trigger接口有两个实现，先说说比较简单的一个PeriodicTrigger。它直接按照给定的时间间隔触发任务执行。更常用的一个触发器是CronTrigger，它使用Cron表达式指定何时执行任务。下面是Spring官方的一个例子。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scheduler.schedule(task, new CronTrigger(&quot;0 15 9-17 * * MON-FRI&quot;));</span><br></pre></td></tr></table></figure>



<p>关于Cron表达式的信息可以参考这篇博客QuartZ Cron表达式。另外还有一个可以在线生成Cron表达式的网站:CroMaker，不过好像需要XX才能访问。而且好像Spring不支持第二个星期一这样的定时器设置，所以如果有这样的需求，需要使用Quartz。</p>
<hr>
<h2 id="配置任务"><a href="#配置任务" class="headerlink" title="配置任务"></a><strong>配置任务</strong></h2><p>**<br>**</p>
<p>任务配置既可以使用Java配置，也可以使用XML配置。不管使用哪种方法，首先需要将要执行的方法所在的类配置为Spring Bean。例如下面就用XML配置注册了两个要执行的任务。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean id&#x3D;&quot;simpleService&quot; class&#x3D;&quot;yitian.study.service.SimpleService&quot;&#x2F;&gt;</span><br><span class="line">&lt;bean id&#x3D;&quot;expensiveTaskService&quot;</span><br><span class="line">      class&#x3D;&quot;yitian.study.service.ExpensiveTaskService&quot;&#x2F;&gt;</span><br></pre></td></tr></table></figure>



<h3 id="Java配置"><a href="#Java配置" class="headerlink" title="Java配置"></a><strong>Java配置</strong></h3><p>**<br>**</p>
<h4 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a><strong>定时任务</strong></h4><p>**<br>**</p>
<p>首先看看Java配置。我们需要在配置类上添加@EnableScheduling，如果需要异步的定时任务，还需要添加@Async。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@EnableAsync</span><br><span class="line">@EnableScheduling</span><br><span class="line">public class TaskConfiguration &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>然后在要执行的方法上添加@Scheduled注解。@Scheduled注解有几个参数，任务会在相应参数的时间下执行。cron参数指定Cron表达式；fixedDelay指定任务执行的间隔，单位是毫秒；initialDelay指定当程序启动后多长时间开始执行第一次任务，单位是毫秒；zone指定任务执行时间所在的时区。下面的例子简单的指定了每隔一秒重复执行一次任务。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public class SimpleService implements IService &#123;</span><br><span class="line">    @Scheduled(fixedDelay &#x3D; 1000)</span><br><span class="line">    @Override</span><br><span class="line">    public void doService() &#123;</span><br><span class="line">        LocalTime time &#x3D; LocalTime.now();</span><br><span class="line">        System.out.println(&quot;This is a simple service:&quot; + time);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="异步任务"><a href="#异步任务" class="headerlink" title="异步任务"></a><strong>异步任务</strong></h4><p>**<br>**</p>
<p>然后是异步任务，如果任务执行时间比较长的话，我们可以考虑使用异步的任务。当调用异步任务的时候，异步方法直接返回，异步任务会交由相应的任务执行器来执行。在Spring中标记异步方法很简单，直接在方法上使用@Async注解。如果需要指定异步方法使用的执行器，可以向注解传递执行器的名称。异步方法可以返回空值。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Async(&quot;otherExecutor&quot;)</span><br><span class="line">void doSomething(String s) &#123;</span><br><span class="line">    &#x2F;&#x2F; this will be executed asynchronously by &quot;otherExecutor&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>但是如果异步方法想返回其他值的话，就必须使用Future。不过不仅是java.util.concurrent.Future，异步方法还可以返回Spring的org.springframework.util.concurrent.ListenableFuture和JDK8的java.util.concurrent.CompletableFuture类型。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Async</span><br><span class="line">Future&lt;String&gt; returnSomething(int i) &#123;</span><br><span class="line">    &#x2F;&#x2F; this will be executed asynchronously</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>异步方法不仅可以用于定时任务中，在Spring的其他地方也可以使用。例如Spring Data JPA可以使用@Async编写异步的查询方法。</p>
<p>需要注意，异步方法没有对应的XML配置，如果我们想让方法是异步的，只能使用注解。当然也不是完全不行，不过就比较麻烦了，你需要使用AsyncExecutionInterceptor和AOP配合才能达到类似的效果。</p>
<p>如果需要处理异步方法的异常，我们需要实现一个AsyncUncaughtExceptionHandler。下面的异步异常处理器简单的打印异常信息。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler &#123;</span><br><span class="line">    @Override</span><br><span class="line">    public void handleUncaughtException(Throwable ex, Method method, Object... params) &#123;</span><br><span class="line">        ex.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>然后通过实现AsyncConfigurer接口（Java配置方式）或者task:annotation-driven（XML配置方式）的exception-handler元素来配置。</p>
<h3 id="XML配置"><a href="#XML配置" class="headerlink" title="XML配置"></a><strong>XML配置</strong></h3><p>**<br>**</p>
<p>Spring提供了task命名空间，让配置定时任务非常简单。</p>
<p>**<br>**</p>
<h4 id="定时器"><a href="#定时器" class="headerlink" title="定时器"></a><strong>定时器</strong></h4><p>**<br>**</p>
<p>task:scheduler会注册一个ThreadPoolTaskScheduler 定时器，它只有一个属性线程池大小。默认是1，我们需要根据任务的数量指定一个合适的大小。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;task:scheduler id&#x3D;&quot;threadPoolTaskScheduler&quot;</span><br><span class="line">                pool-size&#x3D;&quot;10&quot;&#x2F;&gt;</span><br></pre></td></tr></table></figure>



<h4 id="执行器"><a href="#执行器" class="headerlink" title="执行器"></a><strong>执行器</strong></h4><p>**<br>**</p>
<p>task:executor会注册一个ThreadPoolTaskExecutor执行器，我们可以使用它的相关属性来配置该执行器。默认情况下执行队列是无限的，可能会导致JVM使用完所有内存。因此我们最好指定一个确定的数值。还有一个rejection-policy属性，指定执行器队列满时的执行策略：默认是AbortPolicy，直接抛出异常；如果当系统忙时丢弃某些任务是可接受的，可以使用DiscardPolicy或DiscardOldestPolicy策略；当系统负载较重时还可以使用CallerRunsPolicy，它不会将任务交给执行器线程，而是让调用者线程来执行该任务。最后一个就是keep-alive属性，也就是超出线程池数量 线程完成任务之后的存活时间，单位是秒。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;task:executor id&#x3D;&quot;threadPoolTaskExecutor&quot;</span><br><span class="line">               pool-size&#x3D;&quot;10&quot;</span><br><span class="line">               queue-capacity&#x3D;&quot;10&quot;&#x2F;&gt;</span><br></pre></td></tr></table></figure>



<h4 id="执行任务"><a href="#执行任务" class="headerlink" title="执行任务"></a><strong>执行任务</strong></h4><p>**<br>**</p>
<p>执行任务很简单，使用<a href="task:scheduled-tasks">task:scheduled-tasks</a>指定要执行的Bean和方法即可。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;task:scheduled-tasks&gt;</span><br><span class="line">        &lt;task:scheduled ref&#x3D;&quot;simpleService&quot; method&#x3D;&quot;doService&quot;</span><br><span class="line">                        cron&#x3D;&quot;*&#x2F;1 * * * * *&quot;&#x2F;&gt;</span><br><span class="line">        &lt;task:scheduled ref&#x3D;&quot;expensiveTaskService&quot; method&#x3D;&quot;doService&quot;</span><br><span class="line">                        cron&#x3D;&quot;*&#x2F;2 * * * * *&quot;&#x2F;&gt;</span><br><span class="line">    &lt;&#x2F;task:scheduled-tasks&gt;</span><br></pre></td></tr></table></figure>



<p>要设置定时的话，只需要指定相应的属性即可。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;task:scheduled-tasks scheduler&#x3D;&quot;myScheduler&quot;&gt;</span><br><span class="line">    &lt;task:scheduled ref&#x3D;&quot;beanA&quot; method&#x3D;&quot;methodA&quot; fixed-delay&#x3D;&quot;5000&quot; initial-delay&#x3D;&quot;1000&quot;&#x2F;&gt;</span><br><span class="line">    &lt;task:scheduled ref&#x3D;&quot;beanB&quot; method&#x3D;&quot;methodB&quot; fixed-rate&#x3D;&quot;5000&quot;&#x2F;&gt;</span><br><span class="line">    &lt;task:scheduled ref&#x3D;&quot;beanC&quot; method&#x3D;&quot;methodC&quot; cron&#x3D;&quot;*&#x2F;5 * * * * MON-FRI&quot;&#x2F;&gt;</span><br><span class="line">&lt;&#x2F;task:scheduled-tasks&gt;</span><br><span class="line"></span><br><span class="line">&lt;task:scheduler id&#x3D;&quot;myScheduler&quot; pool-size&#x3D;&quot;10&quot;&#x2F;&gt;</span><br></pre></td></tr></table></figure>



<hr>
<h2 id="Quartz集成"><a href="#Quartz集成" class="headerlink" title="Quartz集成"></a><strong>Quartz集成</strong></h2><p>**<br>**</p>
<p>Quartz是一个定时任务的库。Spring也提供了它的支持。Quartz的使用方法请查阅相应文档。这里只简单介绍一下。</p>
<p>Spring的Quartz集成在spring-context-support包中，它还需要Spring事务的支持。因此我们需要下面这样的依赖声明。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">compile group: &#39;org.springframework&#39;, name: &#39;spring-tx&#39;, version: springVersion</span><br><span class="line">   compile group: &#39;org.springframework&#39;, name: &#39;spring-context-support&#39;, version: springVersion</span><br><span class="line">   compile group: &#39;org.quartz-scheduler&#39;, name: &#39;quartz&#39;, version: &#39;2.2.3&#39;</span><br></pre></td></tr></table></figure>



<h3 id="定义任务"><a href="#定义任务" class="headerlink" title="定义任务"></a><strong>定义任务</strong></h3><p>**<br>**</p>
<p>Quartz的任务需要继承Quartz的Job接口。所以一个典型的任务可以写成这样。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public class QuartzService implements IService, Job &#123;</span><br><span class="line">    @Override</span><br><span class="line">    public void doService() &#123;</span><br><span class="line">        System.out.println(&quot;This is a quartz service&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public void execute(JobExecutionContext context) throws JobExecutionException &#123;</span><br><span class="line">        System.out.println(&quot;Do something in execute method of quartz&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="JobDetailFactoryBean"><a href="#JobDetailFactoryBean" class="headerlink" title="JobDetailFactoryBean"></a><strong>JobDetailFactoryBean</strong></h3><p>**<br>**</p>
<p>JobDetailFactoryBean用来定义实现了Job接口的任务。如果需要添加更多信息，可以使用jobDataAsMap属性设置。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean id&#x3D;&quot;jobDetail&quot;</span><br><span class="line">      class&#x3D;&quot;org.springframework.scheduling.quartz.JobDetailFactoryBean&quot;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;jobClass&quot; value&#x3D;&quot;yitian.study.service.QuartzService&quot;&#x2F;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;jobDataAsMap&quot;&gt;</span><br><span class="line">        &lt;map&gt;</span><br><span class="line">            &lt;entry key&#x3D;&quot;timeout&quot; value&#x3D;&quot;10&quot;&#x2F;&gt;</span><br><span class="line">        &lt;&#x2F;map&gt;</span><br><span class="line">    &lt;&#x2F;property&gt;</span><br><span class="line">&lt;&#x2F;bean&gt;</span><br></pre></td></tr></table></figure>



<h3 id="MethodInvokingJobDetailFactoryBean"><a href="#MethodInvokingJobDetailFactoryBean" class="headerlink" title="MethodInvokingJobDetailFactoryBean"></a><strong>MethodInvokingJobDetailFactoryBean</strong></h3><p>**<br>**</p>
<p>如果任务没有实现Job接口，也可以执行，这时候需要使用MethodInvokingJobDetailFactoryBean。如果存在任务对象，使用targetObject属性，如果有任务类，使用targetClass属性。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean id&#x3D;&quot;methodJobDetail&quot; class&#x3D;&quot;org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean&quot;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;targetObject&quot; ref&#x3D;&quot;quartzService&quot;&#x2F;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;targetMethod&quot; value&#x3D;&quot;doService&quot;&#x2F;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;concurrent&quot; value&#x3D;&quot;true&quot;&#x2F;&gt;</span><br><span class="line">&lt;&#x2F;bean&gt;</span><br></pre></td></tr></table></figure>



<h3 id="触发器"><a href="#触发器" class="headerlink" title="触发器"></a><strong>触发器</strong></h3><p>**<br>**</p>
<p>有了任务，就可以定义触发器了。触发器有两个：SimpleTriggerFactoryBean，以指定的间隔重复执行任务；CronTriggerFactoryBean，以给定的Cron表达式执行任务。Quartz的Cron表达式比Spring 的强大，它支持第几个星期几这样的Cron表达式。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean id&#x3D;&quot;simpleTrigger&quot; class&#x3D;&quot;org.springframework.scheduling.quartz.SimpleTriggerFactoryBean&quot;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;jobDetail&quot; ref&#x3D;&quot;jobDetail&quot;&#x2F;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;startDelay&quot; value&#x3D;&quot;0&quot;&#x2F;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;repeatInterval&quot; value&#x3D;&quot;1000&quot;&#x2F;&gt;</span><br><span class="line">&lt;&#x2F;bean&gt;</span><br><span class="line">&lt;bean id&#x3D;&quot;cronTrigger&quot; class&#x3D;&quot;org.springframework.scheduling.quartz.CronTriggerFactoryBean&quot;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;jobDetail&quot; ref&#x3D;&quot;methodJobDetail&quot;&#x2F;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;cronExpression&quot; value&#x3D;&quot;*&#x2F;2 * * * * ?&quot;&#x2F;&gt;</span><br><span class="line">&lt;&#x2F;bean&gt;</span><br></pre></td></tr></table></figure>



<h3 id="执行任务-1"><a href="#执行任务-1" class="headerlink" title="执行任务"></a><strong>执行任务</strong></h3><p>**<br>**</p>
<p>有了触发器，我们就可以执行任务了。注册一个SchedulerFactoryBean，然后将触发器的Bean引用传入即可。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean id&#x3D;&quot;quartzScheduler&quot; class&#x3D;&quot;org.springframework.scheduling.quartz.SchedulerFactoryBean&quot;&gt;</span><br><span class="line">    &lt;property name&#x3D;&quot;triggers&quot;&gt;</span><br><span class="line">        &lt;list&gt;</span><br><span class="line">            &lt;ref bean&#x3D;&quot;cronTrigger&quot;&#x2F;&gt;</span><br><span class="line">            &lt;ref bean&#x3D;&quot;simpleTrigger&quot;&#x2F;&gt;</span><br><span class="line">        &lt;&#x2F;list&gt;</span><br><span class="line">    &lt;&#x2F;property&gt;</span><br><span class="line">&lt;&#x2F;bean&gt;</span><br></pre></td></tr></table></figure>

<p>首先说下这次主题，动态配置。没接触过定时任务的同学可以先看下此篇：</p>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/laoyeye/p/6530791.html">https://www.cnblogs.com/laoyeye/p/6530791.html</a></p>
<p>定时任务实现方式千人千种，不过基础的无外乎 1、JDK 的Timer类 2、Quartz 3、SpringTask  。生产上三种方式我都有使用过。但是使用过程中用的最多的便是xml配置的方式，这种方式最简单，无代码侵入，也比较好理解。</p>
<p>但是却有个致命的缺点，比如你要改某个任务的触发时间，亦或是你要新增一个任务，暂停一个任务。怎么做？</p>
<p>停应用！改XML配置！重新启动！</p>
<p>是不是很致命。最近重新研究了下Quartz的配置，实现了不停机添加、暂停、删除、立即触发任务的方法，在本篇分享出来，其实也不算是完全的研究，在上家公司已经有大佬实现了，这次是也是基于大佬的理解重新实现下。</p>
<p><strong>管理界面：</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/oTKHc6F8tsgtNAMpibdQz4fFJicpnDjOvglRrurO1KKHcmzvicUo2ZFF588gvusEc9iaYNMrILpky5hdt6GpSrZcsw/640?tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img"></p>
<p> 效果图：实在不知道该跑什么了，每隔十秒打一段话吧</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/oTKHc6F8tsgtNAMpibdQz4fFJicpnDjOvggJQ72D7DTSOWHAq53B2NwSibVrbs2Q0cLGJeffDAUGg9E7ciacK9DNnQ/640?tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img"></p>
<h3 id="maven依赖"><a href="#maven依赖" class="headerlink" title="maven依赖"></a><strong>maven依赖</strong></h3><p>使用spring boot做框架支持</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- quartz --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.quartz-scheduler&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;quartz&lt;&#x2F;artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.1&lt;&#x2F;version&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-context-support&lt;&#x2F;artifactId&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br></pre></td></tr></table></figure>

<p>数据表</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE &#96;sys_task&#96; (</span><br><span class="line">  &#96;id&#96; bigint(20) NOT NULL AUTO_INCREMENT,</span><br><span class="line">  &#96;job_name&#96; varchar(255) DEFAULT NULL COMMENT &#39;任务名&#39;,</span><br><span class="line">  &#96;description&#96; varchar(255) DEFAULT NULL COMMENT &#39;任务描述&#39;,</span><br><span class="line">  &#96;cron_expression&#96; varchar(255) DEFAULT NULL COMMENT &#39;cron表达式&#39;,</span><br><span class="line">  &#96;bean_class&#96; varchar(255) DEFAULT NULL COMMENT &#39;任务执行时调用哪个类的方法 包名+类名&#39;,</span><br><span class="line">  &#96;job_status&#96; varchar(255) DEFAULT NULL COMMENT &#39;任务状态&#39;,</span><br><span class="line">  &#96;job_group&#96; varchar(255) DEFAULT NULL COMMENT &#39;任务分组&#39;,</span><br><span class="line">  &#96;create_user&#96; varchar(64) DEFAULT NULL COMMENT &#39;创建者&#39;,</span><br><span class="line">  &#96;create_time&#96; datetime DEFAULT NULL COMMENT &#39;创建时间&#39;,</span><br><span class="line">  &#96;update_user&#96; varchar(64) DEFAULT NULL COMMENT &#39;更新者&#39;,</span><br><span class="line">  &#96;update_time&#96; datetime DEFAULT NULL COMMENT &#39;更新时间&#39;,</span><br><span class="line">  PRIMARY KEY (&#96;id&#96;)</span><br><span class="line">) ENGINE&#x3D;MyISAM AUTO_INCREMENT&#x3D;32 DEFAULT CHARSET&#x3D;utf8;</span><br></pre></td></tr></table></figure>

<p>实现步骤</p>
<p>① 启动项目，启动task监听</p>
<p>② 读取数据库，将开启的任务job和trigger加载到scheduler调度器</p>
<p>③ 根据任务调度运行job类</p>
<p>④ 每次运行利用AdaptableJobFactory实例化job类，以便注入要运行的service</p>
<p>听着是不是很简单，但却还是一头雾水，且听我慢慢道来~~</p>
<h3 id="代码逻辑"><a href="#代码逻辑" class="headerlink" title="代码逻辑"></a>代码逻辑</h3><p><strong>第一步：启动项目，加载监听</strong></p>
<p><strong>Quartz配置</strong></p>
<p>Springboot的配置方法，常规Spring项目可以在xml中配置</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">public class QuartzConfigration &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private JobFactory jobFactory;</span><br><span class="line"></span><br><span class="line">    @Bean</span><br><span class="line">    public SchedulerFactoryBean schedulerFactoryBean() &#123;</span><br><span class="line">        SchedulerFactoryBean schedulerFactoryBean &#x3D; new SchedulerFactoryBean();</span><br><span class="line">        try &#123;</span><br><span class="line">            schedulerFactoryBean.setOverwriteExistingJobs(true);</span><br><span class="line">            schedulerFactoryBean.setQuartzProperties(quartzProperties());</span><br><span class="line">            schedulerFactoryBean.setJobFactory(jobFactory);</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        return schedulerFactoryBean;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 指定quartz.properties，可在配置文件中配置相关属性</span><br><span class="line">    @Bean</span><br><span class="line">    public Properties quartzProperties() throws IOException &#123;</span><br><span class="line">        PropertiesFactoryBean propertiesFactoryBean &#x3D; new PropertiesFactoryBean();</span><br><span class="line">        propertiesFactoryBean.setLocation(new ClassPathResource(&quot;&#x2F;config&#x2F;quartz.properties&quot;));</span><br><span class="line">        propertiesFactoryBean.afterPropertiesSet();</span><br><span class="line">        return propertiesFactoryBean.getObject();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 创建schedule</span><br><span class="line">    @Bean(name &#x3D; &quot;scheduler&quot;)</span><br><span class="line">    public Scheduler scheduler() &#123;</span><br><span class="line">        return schedulerFactoryBean().getScheduler();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>监听器</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">@Component</span><br><span class="line">@Order(value &#x3D; 1)</span><br><span class="line">public class ScheduleJobInitListener implements CommandLineRunner &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    TaskService scheduleJobService;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public void run(String... arg0) throws Exception &#123;</span><br><span class="line">        try &#123;</span><br><span class="line">            scheduleJobService.initSchedule();</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>CommandLineRunner类似Spring框架的ApplicationListener监听器。官方的解释是:</p>
<blockquote>
<p>Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or Order @Order annotation.<br>接口被用作将其加入spring容器中时执行其run方法。多个CommandLineRunner可以被同时执行在同一个spring上下文中并且执行顺序是以order注解的参数顺序一致。</p>
</blockquote>
<p><strong>第二步：读取数据库，加载scheduler调度器</strong></p>
<p>job方法</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">    public void initSchedule() throws SchedulerException &#123;</span><br><span class="line">        &#x2F;&#x2F; 这里获取任务信息数据</span><br><span class="line">        List&lt;TaskDO&gt; jobList &#x3D; taskMapper.list();</span><br><span class="line">        for (TaskDO task : jobList) &#123;</span><br><span class="line">            if (JobStatusEnum.RUNNING.getCode().equals(task.getJobStatus())) &#123;</span><br><span class="line">                quartzManager.addJob(task);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>添加任务到Quartz调度器</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;**</span><br><span class="line">     * 添加任务*&#x2F;</span><br><span class="line">    @SuppressWarnings(&quot;unchecked&quot;)</span><br><span class="line">    public void addJob(TaskDO task) &#123;</span><br><span class="line">        try &#123;</span><br><span class="line">            &#x2F;&#x2F; 创建jobDetail实例，绑定Job实现类</span><br><span class="line">            &#x2F;&#x2F; 指明job的名称，所在组的名称，以及绑定job类</span><br><span class="line">            Class&lt;? extends Job&gt; jobClass &#x3D; (Class&lt;? extends Job&gt;) (Class.forName(task.getBeanClass()).newInstance()</span><br><span class="line">                    .getClass());</span><br><span class="line">            JobDetail jobDetail &#x3D; JobBuilder.newJob(jobClass).withIdentity(task.getJobName(), task.getJobGroup())&#x2F;&#x2F; 任务名称和组构成任务key</span><br><span class="line">                    .build();</span><br><span class="line">            &#x2F;&#x2F; 定义调度触发规则</span><br><span class="line">            &#x2F;&#x2F; 使用cornTrigger规则</span><br><span class="line">            Trigger trigger &#x3D; TriggerBuilder.newTrigger().withIdentity(task.getJobName(), task.getJobGroup())&#x2F;&#x2F; 触发器key</span><br><span class="line">                    .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))</span><br><span class="line">                    .withSchedule(CronScheduleBuilder.cronSchedule(task.getCronExpression())).startNow().build();</span><br><span class="line">            &#x2F;&#x2F; 把作业和触发器注册到任务调度中</span><br><span class="line">            scheduler.scheduleJob(jobDetail, trigger);</span><br><span class="line">            &#x2F;&#x2F; 启动</span><br><span class="line">            if (!scheduler.isShutdown()) &#123;</span><br><span class="line">                scheduler.start();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>Scheduler作为Quartz的核心调度器，有将近50多个API接口，包括任务的添加，暂停，恢复，删除等一系列的API，这里仅介绍一些常用的，想要了解更多可以稍后看下彩蛋部分。</p>
<p>1、start()方法：只有调用start()方法后，Scheduler线程才开始启动触发器trigger，运行job</p>
<p>2、pauseJob(JobKey jobKey) ：根据指定的JobDetail key暂停job。</p>
<p>3、resumeJob(JobKey jobKey) ：根据指定的key恢复一个job。</p>
<p>4、deleteJob(JobKey jobKey) ：删除一个job</p>
<p>5、triggerJob(JobKey jobKey) ：触发一个JobDetail(现在执行)。</p>
<p>6、rescheduleJob(TriggerKey triggerKey, Trigger newTrigger)：</p>
<p>用给定的键删除触发器，并存储新的触发器，它必须与同一个作业相关联（新触发器必须具有指定的作业名和组）-然而，新触发器不必具有与旧触发器相同的名称。</p>
<p><strong>第三步：根据任务调度运行job类</strong></p>
<p>其实这一步是不需要我们编写的，在我们将正确的JobDetail 和 Trigger 表达式加载到任务调度后，调度器会自动触发任务的执行</p>
<p><strong>第四步：实例化job类，注入要运行的service</strong></p>
<p>工厂类</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">@Component</span><br><span class="line">public class JobFactory extends AdaptableJobFactory &#123;</span><br><span class="line">    &#x2F;&#x2F;这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴.</span><br><span class="line">    &#x2F;&#x2F;为什么需要这个类呢，在我写的这个demo中，大家可以将此类删掉，发现程序也可以正确运行，可是我为什么还是加上呢。</span><br><span class="line">    &#x2F;&#x2F;大家可以看下我们的任务类，大家可以看到Job对象的实例化过程是在Quartz中进行的，这时候我们将spring的东西注入进来，肯定是行不通的，所以需要这个类</span><br><span class="line">    @Autowired</span><br><span class="line">    private AutowireCapableBeanFactory capableBeanFactory;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception &#123;</span><br><span class="line">        &#x2F;&#x2F;调用父类的方法</span><br><span class="line">        Object jobInstance &#x3D; super.createJobInstance(bundle);</span><br><span class="line">        &#x2F;&#x2F;进行注入</span><br><span class="line">        capableBeanFactory.autowireBean(jobInstance);</span><br><span class="line">        return jobInstance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>任务类</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@DisallowConcurrentExecution &#x2F;&#x2F;作业不并发</span><br><span class="line">@Component</span><br><span class="line">public class HelloWorldJob implements Job&#123;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public void execute(JobExecutionContext arg0) throws JobExecutionException &#123;</span><br><span class="line">        </span><br><span class="line">        System.out.println(&quot;欢迎使用yyblog,这是一个定时任务 --小卖铺的老爷爷!&quot;+ DateUtils.fullTime(new Date()));</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p> 好了，大功告成，一个简单的动态配置的定时任务已经完成。是不是so easy，下面我们再来简单实现下其他的几种常用的api吧。</p>
<p>暂停一个job</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public void pauseJob(TaskDO task) throws SchedulerException &#123;</span><br><span class="line">        JobKey jobKey &#x3D; JobKey.jobKey(task.getJobName(), task.getJobGroup());</span><br><span class="line">        scheduler.pauseJob(jobKey);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>恢复一个job</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public void resumeJob(TaskDO task) throws SchedulerException &#123;</span><br><span class="line">        JobKey jobKey &#x3D; JobKey.jobKey(task.getJobName(), task.getJobGroup());</span><br><span class="line">        scheduler.resumeJob(jobKey);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>删除一个job</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public void deleteJob(TaskDO task) throws SchedulerException &#123;</span><br><span class="line">        JobKey jobKey &#x3D; JobKey.jobKey(task.getJobName(), task.getJobGroup());</span><br><span class="line">        scheduler.deleteJob(jobKey);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>立即触发job</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public void runJobNow(TaskDO task) throws SchedulerException &#123;</span><br><span class="line">        JobKey jobKey &#x3D; JobKey.jobKey(task.getJobName(), task.getJobGroup());</span><br><span class="line">        scheduler.triggerJob(jobKey);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>更新job表达式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public void updateJobCron(TaskDO task) throws SchedulerException &#123;</span><br><span class="line"></span><br><span class="line">        TriggerKey triggerKey &#x3D; TriggerKey.triggerKey(task.getJobName(), task.getJobGroup());</span><br><span class="line"></span><br><span class="line">        CronTrigger trigger &#x3D; (CronTrigger) scheduler.getTrigger(triggerKey);</span><br><span class="line"></span><br><span class="line">        CronScheduleBuilder scheduleBuilder &#x3D; CronScheduleBuilder.cronSchedule(task.getCronExpression());</span><br><span class="line"></span><br><span class="line">        trigger &#x3D; trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();</span><br><span class="line"></span><br><span class="line">        scheduler.rescheduleJob(triggerKey, trigger);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>好了，正文部分基本上就这些，不多废话了，本文没有太多的讲解原理，只是简单的应用，水平不够还望大家见谅。</p>
<p>看了上面的效果图是不是还是有种看的见摸不着的感觉，老爷爷这里也做了个线上的实例供大家体验~有兴趣的同学可以亲自试验一下。</p>
<p>实例地址：<a target="_blank" rel="noopener" href="http://www.laoyeye.net/management/index">http://www.laoyeye.net/management/index</a>  账号/密码：test/123456  菜单：系统设置/计划任务</p>
<p> 当然了还有最重要的源码和API文档我也不会忘的。</p>
<p>源码地址：<a target="_blank" rel="noopener" href="https://github.com/allanzhuo/yyblog">https://github.com/allanzhuo/yyblog</a></p>
<p>Quartz文档地址：<a target="_blank" rel="noopener" href="https://github.com/allanzhuo/yyblog/tree/master/doc">https://github.com/allanzhuo/yyblog/tree/master/doc</a></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-develop/别用Date了，Java8新特性之日期处理，现在学会也不迟"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/develop/%E5%88%AB%E7%94%A8Date%E4%BA%86%EF%BC%8CJava8%E6%96%B0%E7%89%B9%E6%80%A7%E4%B9%8B%E6%97%A5%E6%9C%9F%E5%A4%84%E7%90%86%EF%BC%8C%E7%8E%B0%E5%9C%A8%E5%AD%A6%E4%BC%9A%E4%B9%9F%E4%B8%8D%E8%BF%9F/"
    >别用Date了，Java8新特性之日期处理，现在学会也不迟.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/%E5%88%AB%E7%94%A8Date%E4%BA%86%EF%BC%8CJava8%E6%96%B0%E7%89%B9%E6%80%A7%E4%B9%8B%E6%97%A5%E6%9C%9F%E5%A4%84%E7%90%86%EF%BC%8C%E7%8E%B0%E5%9C%A8%E5%AD%A6%E4%BC%9A%E4%B9%9F%E4%B8%8D%E8%BF%9F/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="别用Date了，Java8新特性之日期处理，现在学会也不迟"><a href="#别用Date了，Java8新特性之日期处理，现在学会也不迟" class="headerlink" title="别用Date了，Java8新特性之日期处理，现在学会也不迟"></a>别用Date了，Java8新特性之日期处理，现在学会也不迟</h1><p>前天小Hub发了一篇《<a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzIwODkzOTc1MQ==&mid=2247484420&idx=1&sn=8e7ce445a95a6121e40d7931dbdc98c4&chksm=977a3b8ca00db29a12cf7a0c205a4eac49ae65bd341c5e48fb26a7399a3966b06d1ac2152fc6&scene=21#wechat_redirect">SpringBoot 全局日期格式化（基于注解）</a>》文章，很多读者都指出了文中几个不严谨的地方，其中关于实体类中使用Date类型线程不安全。在JDK8中我们已经开始使用LocalDateTime，那么对于这个类的用法，如果有同学不了解的话，来看看下面的文章，小Hub看了一遍，觉得作者写得很详细了，对比式写法，更加通俗易懂！</p>
<p>看完别忘了在文末右下角点个[在看]赞一赞噢~</p>
<hr>
<blockquote>
<p>作者：WayfreemBlog</p>
<p>来源：segmentfault.com/a/1190000012922933</p>
</blockquote>
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>伴随 <code>lambda表达式</code>、 <code>streams</code> 以及一系列小优化，Java 8 推出了全新的日期时间API。</p>
<p>Java处理日期、日历和时间的不足之处：将 java.util.Date 设定为可变类型，以及 SimpleDateFormat 的非线程安全使其应用非常受限。然后就在 java8 上面增加新的特性。</p>
<p>全新API的众多好处之一就是，明确了日期时间概念，例如： <code>瞬时（instant）</code>、 <code>长短（duration）</code>、 <code>日期</code>、 <code>时间</code>、 <code>时区</code>和 <code>周期</code>。</p>
<p>同时继承了Joda 库按人类语言和计算机各自解析的时间处理方式。不同于老版本，新API基于ISO标准日历系统，java.time包下的所有类都是不可变类型而且线程安全。</p>
<h3 id="关键类"><a href="#关键类" class="headerlink" title="关键类"></a>关键类</h3><ul>
<li>Instant：瞬时实例。</li>
<li>LocalDate：本地日期，不包含具体时间 例如：2014-01-14 可以用来记录生日、纪念日、加盟日等。</li>
<li>LocalTime：本地时间，不包含日期。</li>
<li>LocalDateTime：组合了日期和时间，但不包含时差和时区信息。</li>
<li>ZonedDateTime：最完整的日期时间，包含时区和相对UTC或格林威治的时差。</li>
</ul>
<p>新API还引入了 ZoneOffSet 和 ZoneId 类，使得解决时区问题更为简便。解析、格式化时间的 DateTimeFormatter 类也全部重新设计。</p>
<h3 id="实战"><a href="#实战" class="headerlink" title="实战"></a>实战</h3><p>在教程中我们将通过一些简单的实例来学习如何使用新API，因为只有在实际的项目中用到，才是学习新知识以及新技术最快的方式。</p>
<h3 id="1-获取当前的日期"><a href="#1-获取当前的日期" class="headerlink" title="1. 获取当前的日期"></a>1. 获取当前的日期</h3><p>Java 8 中的 <code>LocalDate</code> 用于表示当天日期。和 java.util.Date不同，它只有日期，不包含时间。当你仅需要表示日期时就用这个类。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;获取今天的日期public void getCurrentDate()&#123;    LocalDate today &#x3D; LocalDate.now();    System.out.println(&quot;Today&#39;s Local date : &quot; + today);</span><br><span class="line">    &#x2F;&#x2F;这个是作为对比    Date date &#x3D; new Date();    System.out.println(date);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541141.webp" alt="clipboard.png"></p>
<p>上面的代码创建了当天的日期，不含时间信息。打印出的日期格式非常友好，不像 Date类 打印出一堆没有格式化的信息。</p>
<h3 id="2-获取年、月、日信息"><a href="#2-获取年、月、日信息" class="headerlink" title="2. 获取年、月、日信息"></a>2. 获取年、月、日信息</h3><p><code>LocalDate</code> 提供了获取年、月、日的快捷方法，其实例还包含很多其它的日期属性。通过调用这些方法就可以很方便的得到需要的日期信息，不用像以前一样需要依赖java.util.Calendar类了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;获取年、月、日信息public void getDetailDate()&#123;    LocalDate today &#x3D; LocalDate.now();    int year &#x3D; today.getYear();    int month &#x3D; today.getMonthValue();    int day &#x3D; today.getDayOfMonth();</span><br><span class="line">    System.out.printf(&quot;Year : %d  Month : %d  day : %d t %n&quot;, year, month, day);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167544945.webp" alt="clipboard.png"></p>
<h3 id="3-处理特定日期"><a href="#3-处理特定日期" class="headerlink" title="3.处理特定日期"></a>3.处理特定日期</h3><p>在第一个例子里，我们通过静态工厂方法now()非常容易地创建了当天日期。</p>
<p>我们还可以调用另一个有用的工厂方法 <code>LocalDate.of()</code> 创建任意日期， 该方法需要传入年、月、日做参数，返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误，比如年度起始于1900，月份是从 <code>0</code> 开始等等。日期所见即所得，就像下面这个例子表示了1月21日，直接明了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;处理特定日期public void handleSpecilDate()&#123;    LocalDate dateOfBirth &#x3D; LocalDate.of(2018, 01, 21);    System.out.println(&quot;The specil date is : &quot; + dateOfBirth);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541136.webp" alt="clipboard.png"></p>
<h3 id="4-判断两个日期是否相等"><a href="#4-判断两个日期是否相等" class="headerlink" title="4.判断两个日期是否相等"></a>4.判断两个日期是否相等</h3><p>现实生活中有一类时间处理就是判断两个日期是否相等。在项目开发的时候总会遇到这样子的问题。</p>
<p>下面这个例子会帮助你用Java 8的方式去解决， <code>LocalDate</code> 重载了equal方法。</p>
<p>注意，如果比较的日期是字符型的，需要先解析成日期对象再作判断。</p>
<p>请看下面的例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;判断两个日期是否相等public void compareDate()&#123;    LocalDate today &#x3D; LocalDate.now();    LocalDate date1 &#x3D; LocalDate.of(2018, 01, 21);</span><br><span class="line">    if(date1.equals(today))&#123;           System.out.printf(&quot;TODAY %s and DATE1 %s are same date %n&quot;, today, date1);    &#125;&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541138.webp" alt="clipboard.png"></p>
<h3 id="5-检查像生日这种周期性事件"><a href="#5-检查像生日这种周期性事件" class="headerlink" title="5.检查像生日这种周期性事件"></a>5.检查像生日这种周期性事件</h3><p>Java 中另一个日期时间的处理就是检查类似生日、纪念日、法定假日（国庆以及春节）、或者每个月固定时间发送邮件给客户 这些周期性事件。</p>
<p>Java中如何检查这些节日或其它周期性事件呢？答案就是 <code>MonthDay</code>类。这个类组合了月份和日，去掉了年，这意味着你可以用它判断每年都会发生事件。</p>
<p>和这个类相似的还有一个 <code>YearMonth</code>类。这些类也都是不可变并且线程安全的值类型。下面我们通过 <code>MonthDay</code>来检查周期性事件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;处理周期性的日期public void cycleDate()&#123;    LocalDate today &#x3D; LocalDate.now();    LocalDate dateOfBirth &#x3D; LocalDate.of(2018, 01, 21);</span><br><span class="line">    MonthDay birthday &#x3D; MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());    MonthDay currentMonthDay &#x3D; MonthDay.from(today);</span><br><span class="line">    if(currentMonthDay.equals(birthday))&#123;       System.out.println(&quot;Many Many happy returns of the day !!&quot;);    &#125;else&#123;       System.out.println(&quot;Sorry, today is not your birthday&quot;);    &#125;&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167544847.webp" alt="clipboard.png"></p>
<h3 id="6-获取当前时间"><a href="#6-获取当前时间" class="headerlink" title="6.获取当前时间"></a>6.获取当前时间</h3><p>与 获取日期 例子很像，获取时间使用的是 <code>LocalTime</code> 类，一个只有时间没有日期的LocalDate近亲。可以调用静态工厂方法now()来获取当前时间。默认的格式是 <code>hh:mm:ss:nnn</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;获取当前时间public void getCurrentTime()&#123;    LocalTime time &#x3D; LocalTime.now();    System.out.println(&quot;local time now : &quot; + time);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167544782.webp" alt="clipboard.png"></p>
<h3 id="7-在现有的时间上增加小时"><a href="#7-在现有的时间上增加小时" class="headerlink" title="7.在现有的时间上增加小时"></a>7.在现有的时间上增加小时</h3><p>Java 8 提供了更好的 plusHours() 方法替换 add() ，并且是兼容的。注意，这些方法返回一个全新的LocalTime实例，由于其不可变性，返回后一定要用变量赋值。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;增加小时public void plusHours()&#123;    LocalTime time &#x3D; LocalTime.now();    LocalTime newTime &#x3D; time.plusHours(2); &#x2F;&#x2F; 增加两小时    System.out.println(&quot;Time after 2 hours : &quot; +  newTime);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541140.webp" alt="clipboard.png"></p>
<p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzI4OTA3NDQ0Nw==&mid=2455548022&idx=1&sn=572e8a1ebe18e9f012d99880bc977da6&scene=21#wechat_redirect">分享一套SpringBoot开发博客系统源码，以及完整开发文档！速度保存！</a></p>
<h3 id="8-如何计算一个星期之后的日期"><a href="#8-如何计算一个星期之后的日期" class="headerlink" title="8.如何计算一个星期之后的日期"></a>8.如何计算一个星期之后的日期</h3><p>和上个例子计算两小时以后的时间类似，这个例子会计算一周后的日期。LocalDate日期不包含时间信息，它的plus()方法用来增加天、周、月，ChronoUnit类声明了这些时间单位。由于LocalDate也是不变类型，返回后一定要用变量赋值。</p>
<p>可以用同样的方法增加1个月、1年、1小时、1分钟甚至一个世纪，更多选项可以查看Java 8 API中的ChronoUnit类。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;如何计算一周后的日期public void nextWeek()&#123;    LocalDate today &#x3D; LocalDate.now();    LocalDate nextWeek &#x3D; today.plus(1, ChronoUnit.WEEKS);    &#x2F;&#x2F;使用变量赋值    System.out.println(&quot;Today is : &quot; + today);    System.out.println(&quot;Date after 1 week : &quot; + nextWeek);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601366701101.webp" alt="clipboard.png"></p>
<h3 id="9-计算一年前或一年后的日期"><a href="#9-计算一年前或一年后的日期" class="headerlink" title="9.计算一年前或一年后的日期"></a>9.计算一年前或一年后的日期</h3><p>接着上面的例子中我们通过 <code>LocalDate</code> 的 <code>plus()</code> 方法增加天数、周数或月数，这个例子我们利用 <code>minus()</code> 方法计算一年前的日期。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;计算一年前或一年后的日期public void minusDate()&#123;    LocalDate today &#x3D; LocalDate.now();    LocalDate previousYear &#x3D; today.minus(1, ChronoUnit.YEARS);    System.out.println(&quot;Date before 1 year : &quot; + previousYear);</span><br><span class="line">    LocalDate nextYear &#x3D; today.plus(1, ChronoUnit.YEARS);    System.out.println(&quot;Date after 1 year : &quot; + nextYear);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541143.webp" alt="clipboard.png"></p>
<h3 id="10-使用Java-8的Clock时钟类"><a href="#10-使用Java-8的Clock时钟类" class="headerlink" title="10.使用Java 8的Clock时钟类"></a>10.使用Java 8的Clock时钟类</h3><p>Java 8增加了一个 Clock 时钟类用于获取当时的时间戳，或当前时区下的日期时间信息。以前用到System.currentTimeInMillis() 和 TimeZone.getDefault() 的地方都可用Clock替换。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">public void clock()&#123;    &#x2F;&#x2F; 根据系统时间返回当前时间并设置为UTC。    Clock clock &#x3D; Clock.systemUTC();    System.out.println(&quot;Clock : &quot; + clock);</span><br><span class="line">    &#x2F;&#x2F; 根据系统时钟区域返回时间    Clock defaultClock &#x3D; Clock.systemDefaultZone();    System.out.println(&quot;Clock : &quot; + clock);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541210.webp" alt="clipboard.png"></p>
<h3 id="11-判断日期是早于还是晚于另一个日期"><a href="#11-判断日期是早于还是晚于另一个日期" class="headerlink" title="11.判断日期是早于还是晚于另一个日期"></a>11.判断日期是早于还是晚于另一个日期</h3><p>LocalDate 类有两类方法 <code>isBefore()</code> 和 <code>isAfter()</code> 用于比较日期。调用 <code>isBefore()</code> 方法时，如果给定日期小于当前日期则返回 true。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;如何用Java判断日期是早于还是晚于另一个日期public void isBeforeOrIsAfter()&#123;    LocalDate today &#x3D; LocalDate.now();</span><br><span class="line">    LocalDate tomorrow &#x3D; LocalDate.of(2018, 1, 29);    if(tomorrow.isAfter(today))&#123;        System.out.println(&quot;Tomorrow comes after today&quot;);    &#125;</span><br><span class="line">    &#x2F;&#x2F;减去一天    LocalDate yesterday &#x3D; today.minus(1, ChronoUnit.DAYS);</span><br><span class="line">    if(yesterday.isBefore(today))&#123;        System.out.println(&quot;Yesterday is day before today&quot;);    &#125;&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541137.webp" alt="clipboard.png"></p>
<h3 id="12-处理时区"><a href="#12-处理时区" class="headerlink" title="12.处理时区"></a>12.处理时区</h3><p>Java 8不仅分离了日期和时间，也把时区分离出来了。现在有一系列单独的类如 ZoneId 来处理特定时区，ZoneDateTime 类来表示某时区下的时间。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;获取特定时区下面的时间public void getZoneTime()&#123;    &#x2F;&#x2F;设置时区    ZoneId america &#x3D; ZoneId.of(&quot;America&#x2F;New_York&quot;);</span><br><span class="line">    LocalDateTime localtDateAndTime &#x3D; LocalDateTime.now();</span><br><span class="line">    ZonedDateTime dateAndTimeInNewYork  &#x3D; ZonedDateTime.of(localtDateAndTime, america );    System.out.println(&quot;现在的日期和时间在特定的时区 : &quot; + dateAndTimeInNewYork);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167548750.webp" alt="clipboard.png"></p>
<h3 id="13-如何体现出固定日期"><a href="#13-如何体现出固定日期" class="headerlink" title="13.如何体现出固定日期"></a>13.如何体现出固定日期</h3><p>例如：表示信用卡到期这类固定日期。与 MonthDay 检查重复事件的例子相似， <code>YearMonth</code> 是另一个组合类，用于表示信用卡到期日、FD到期日、期货期权到期日等。</p>
<p>还可以用这个类得到 当月共有多少天，YearMonth 实例的 <code>lengthOfMonth()</code> 方法可以返回当月的天数，在判断2月有28天还是29天时非常有用。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;使用 YearMonth类处理特定的日期public void checkCardExpiry()&#123;    YearMonth currentYearMonth &#x3D; YearMonth.now();    System.out.printf(&quot;Days in month year %s: %d%n&quot;, currentYearMonth, currentYearMonth.lengthOfMonth());</span><br><span class="line">    YearMonth creditCardExpiry &#x3D; YearMonth.of(2028, Month.FEBRUARY);    System.out.printf(&quot;Your credit card expires on %s %n&quot;, creditCardExpiry);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541039.webp" alt="clipboard.png"></p>
<h3 id="14-检查闰年"><a href="#14-检查闰年" class="headerlink" title="14.检查闰年"></a>14.检查闰年</h3><p>LocalDate类有一个很实用的方法 <code>isLeapYear()</code> 判断该实例是否是一个闰年，如果你还是想重新发明轮子，这有一个代码示例，纯Java逻辑编写的判断闰年的程序。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;检查闰年public void isLeapYear()&#123;    LocalDate today &#x3D; LocalDate.now();    if(today.isLeapYear())&#123;        System.out.println(&quot;This year is Leap year&quot;);    &#125;else &#123;        System.out.println(&quot;2018 is not a Leap year&quot;);    &#125;&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541204.webp" alt="clipboard.png"></p>
<h3 id="15-计算两个日期之间的天数和月数"><a href="#15-计算两个日期之间的天数和月数" class="headerlink" title="15.计算两个日期之间的天数和月数"></a>15.计算两个日期之间的天数和月数</h3><p>有一个常见日期操作是计算两个日期之间的天数、周数或月数。在Java 8中可以用java.time.Period类来做计算。下面这个例子中，我们计算了当天和将来某一天之间的月数。</p>
<p>下面的例子：现在是一月份，距离到五月份，中间相隔3月</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;计算两个日期之间的天数和月数public void calcDateDays()&#123;    LocalDate today &#x3D; LocalDate.now();</span><br><span class="line">    LocalDate java8Release &#x3D; LocalDate.of(2018, Month.MAY, 14);</span><br><span class="line">    Period periodToNextJavaRelease &#x3D; Period.between(today, java8Release);</span><br><span class="line">    System.out.println(&quot;Months left between today and Java 8 release : &quot;                                           + periodToNextJavaRelease.getMonths() );&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541139.png" alt="clipboard.png"></p>
<h3 id="16-包含时差信息的日期和时间"><a href="#16-包含时差信息的日期和时间" class="headerlink" title="16.包含时差信息的日期和时间"></a>16.包含时差信息的日期和时间</h3><p>ZoneOffset类用来表示时区，举例来说印度与GMT或UTC标准时区相差+05:30，可以通过ZoneOffset.of()静态方法来 获取对应的时区。一旦得到了时差就可以通过传入LocalDateTime和ZoneOffset来创建一个OffSetDateTime对象。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public void ZoneOffset()&#123;    LocalDateTime datetime &#x3D; LocalDateTime.of(2018, Month.FEBRUARY, 14, 19, 30);    ZoneOffset offset &#x3D; ZoneOffset.of(&quot;+05:30&quot;);    OffsetDateTime date &#x3D; OffsetDateTime.of(datetime, offset);    System.out.println(&quot;Date and Time with timezone offset in Java : &quot; + date);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541135.png" alt="clipboard.png"></p>
<h3 id="17-获取当前的时间戳"><a href="#17-获取当前的时间戳" class="headerlink" title="17.获取当前的时间戳"></a>17.获取当前的时间戳</h3><p>Instant类有一个静态工厂方法now()会返回当前的时间戳，如下所示：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public void getTimestamp()&#123;    Instant timestamp &#x3D; Instant.now();    System.out.println(&quot;What is value of this instant &quot; + timestamp);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541142.png" alt="clipboard.png"></p>
<h3 id="18-使用预定义的格式化工具去解析或格式化日期"><a href="#18-使用预定义的格式化工具去解析或格式化日期" class="headerlink" title="18.使用预定义的格式化工具去解析或格式化日期"></a>18.使用预定义的格式化工具去解析或格式化日期</h3><p>Java 8引入了全新的日期时间格式工具，线程安全而且使用方便。它自带了一些常用的内置格式化工具。</p>
<p>下面这个例子使用了BASICISODATE格式化工具将2018年2月10日格式化成20180210。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 使用预定义的格式化工具去解析或格式化日期public void formateDate()&#123;    String dayAfterTommorrow &#x3D; &quot;20180210&quot;;    LocalDate formatted &#x3D; LocalDate.parse(dayAfterTommorrow, DateTimeFormatter.BASIC_ISO_DATE);    System.out.printf(&quot;Date generated from String %s is %s %n&quot;, dayAfterTommorrow, formatted);&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601167541218.webp" alt="clipboard.png"></p>
<h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>最后附上全部代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">package com.wq.study.java8.date;</span><br><span class="line">import java.time.Clock;import java.time.Instant;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.Month;import java.time.MonthDay;import java.time.OffsetDateTime;import java.time.ZoneOffset;import java.time.Period;import java.time.YearMonth;import java.time.ZoneId;import java.time.ZonedDateTime;import java.time.format.DateTimeFormatter;import java.time.temporal.ChronoUnit;import java.util.Date;</span><br><span class="line">public class DateTest &#123;</span><br><span class="line">    &#x2F;&#x2F;获取今天的日期    public void getCurrentDate()&#123;        LocalDate today &#x3D; LocalDate.now();        System.out.println(&quot;Today&#39;s Local date : &quot; + today);</span><br><span class="line">        &#x2F;&#x2F;这个是作为对比        Date date &#x3D; new Date();        System.out.println(date);    &#125;</span><br><span class="line">    &#x2F;&#x2F;获取年、月、日信息    public void getDetailDate()&#123;        LocalDate today &#x3D; LocalDate.now();        int year &#x3D; today.getYear();        int month &#x3D; today.getMonthValue();        int day &#x3D; today.getDayOfMonth();</span><br><span class="line">        System.out.printf(&quot;Year : %d  Month : %d  day : %d t %n&quot;, year, month, day);    &#125;</span><br><span class="line">    &#x2F;&#x2F;处理特定日期    public void handleSpecilDate()&#123;        LocalDate dateOfBirth &#x3D; LocalDate.of(2018, 01, 21);        System.out.println(&quot;The specil date is : &quot; + dateOfBirth);    &#125;</span><br><span class="line">    &#x2F;&#x2F;判断两个日期是否相等    public void compareDate()&#123;        LocalDate today &#x3D; LocalDate.now();        LocalDate date1 &#x3D; LocalDate.of(2018, 01, 21);</span><br><span class="line">        if(date1.equals(today))&#123;            System.out.printf(&quot;TODAY %s and DATE1 %s are same date %n&quot;, today, date1);        &#125;    &#125;</span><br><span class="line">    &#x2F;&#x2F;处理周期性的日期    public void cycleDate()&#123;        LocalDate today &#x3D; LocalDate.now();        LocalDate dateOfBirth &#x3D; LocalDate.of(2018, 01, 21);</span><br><span class="line">        MonthDay birthday &#x3D; MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());        MonthDay currentMonthDay &#x3D; MonthDay.from(today);</span><br><span class="line">        if(currentMonthDay.equals(birthday))&#123;           System.out.println(&quot;Many Many happy returns of the day !!&quot;);        &#125;else&#123;           System.out.println(&quot;Sorry, today is not your birthday&quot;);        &#125;    &#125;</span><br><span class="line">    &#x2F;&#x2F;获取当前时间    public void getCurrentTime()&#123;        LocalTime time &#x3D; LocalTime.now();        System.out.println(&quot;local time now : &quot; + time);    &#125;</span><br><span class="line">    &#x2F;&#x2F;增加小时    public void plusHours()&#123;        LocalTime time &#x3D; LocalTime.now();        LocalTime newTime &#x3D; time.plusHours(2); &#x2F;&#x2F; 增加两小时        System.out.println(&quot;Time after 2 hours : &quot; +  newTime);    &#125;</span><br><span class="line">    &#x2F;&#x2F;如何计算一周后的日期    public void nextWeek()&#123;        LocalDate today &#x3D; LocalDate.now();        LocalDate nextWeek &#x3D; today.plus(1, ChronoUnit.WEEKS);        System.out.println(&quot;Today is : &quot; + today);        System.out.println(&quot;Date after 1 week : &quot; + nextWeek);    &#125;</span><br><span class="line">    &#x2F;&#x2F;计算一年前或一年后的日期    public void minusDate()&#123;        LocalDate today &#x3D; LocalDate.now();        LocalDate previousYear &#x3D; today.minus(1, ChronoUnit.YEARS);        System.out.println(&quot;Date before 1 year : &quot; + previousYear);</span><br><span class="line">        LocalDate nextYear &#x3D; today.plus(1, ChronoUnit.YEARS);        System.out.println(&quot;Date after 1 year : &quot; + nextYear);    &#125;</span><br><span class="line">    public void clock()&#123;        &#x2F;&#x2F; 根据系统时间返回当前时间并设置为UTC。        Clock clock &#x3D; Clock.systemUTC();        System.out.println(&quot;Clock : &quot; + clock);</span><br><span class="line">        &#x2F;&#x2F; 根据系统时钟区域返回时间        Clock defaultClock &#x3D; Clock.systemDefaultZone();        System.out.println(&quot;Clock : &quot; + clock);    &#125;</span><br><span class="line">    &#x2F;&#x2F;如何用Java判断日期是早于还是晚于另一个日期    public void isBeforeOrIsAfter()&#123;        LocalDate today &#x3D; LocalDate.now();</span><br><span class="line">        LocalDate tomorrow &#x3D; LocalDate.of(2018, 1, 29);        if(tomorrow.isAfter(today))&#123;            System.out.println(&quot;Tomorrow comes after today&quot;);        &#125;</span><br><span class="line">        LocalDate yesterday &#x3D; today.minus(1, ChronoUnit.DAYS);</span><br><span class="line">        if(yesterday.isBefore(today))&#123;            System.out.println(&quot;Yesterday is day before today&quot;);        &#125;    &#125;</span><br><span class="line">    &#x2F;&#x2F;时区处理    public void getZoneTime()&#123;        &#x2F;&#x2F;设置时区        ZoneId america &#x3D; ZoneId.of(&quot;America&#x2F;New_York&quot;);</span><br><span class="line">        LocalDateTime localtDateAndTime &#x3D; LocalDateTime.now();</span><br><span class="line">        ZonedDateTime dateAndTimeInNewYork  &#x3D; ZonedDateTime.of(localtDateAndTime, america );        System.out.println(&quot;现在的日期和时间在特定的时区 : &quot; + dateAndTimeInNewYork);    &#125;</span><br><span class="line">    &#x2F;&#x2F;使用 YearMonth类处理特定的日期    public void checkCardExpiry()&#123;        YearMonth currentYearMonth &#x3D; YearMonth.now();        System.out.printf(&quot;Days in month year %s: %d%n&quot;, currentYearMonth, currentYearMonth.lengthOfMonth());</span><br><span class="line">        YearMonth creditCardExpiry &#x3D; YearMonth.of(2028, Month.FEBRUARY);        System.out.printf(&quot;Your credit card expires on %s %n&quot;, creditCardExpiry);    &#125;</span><br><span class="line">    &#x2F;&#x2F;检查闰年    public void isLeapYear()&#123;        LocalDate today &#x3D; LocalDate.now();        if(today.isLeapYear())&#123;           System.out.println(&quot;This year is Leap year&quot;);        &#125;else &#123;            System.out.println(&quot;2018 is not a Leap year&quot;);        &#125;    &#125;</span><br><span class="line">    &#x2F;&#x2F;计算两个日期之间的天数和月数    public void calcDateDays()&#123;        LocalDate today &#x3D; LocalDate.now();</span><br><span class="line">        LocalDate java8Release &#x3D; LocalDate.of(2018, Month.MAY, 14);</span><br><span class="line">        Period periodToNextJavaRelease &#x3D; Period.between(today, java8Release);</span><br><span class="line">        System.out.println(&quot;Months left between today and Java 8 release : &quot;                                           + periodToNextJavaRelease.getMonths() );    &#125;</span><br><span class="line">    &#x2F;&#x2F; 包含时差信息的日期和时间    public void ZoneOffset()&#123;        LocalDateTime datetime &#x3D; LocalDateTime.of(2018, Month.FEBRUARY, 14, 19, 30);        ZoneOffset offset &#x3D; ZoneOffset.of(&quot;+05:30&quot;);        OffsetDateTime date &#x3D; OffsetDateTime.of(datetime, offset);        System.out.println(&quot;Date and Time with timezone offset in Java : &quot; + date);    &#125;</span><br><span class="line">    &#x2F;&#x2F; 获取时间戳    public void getTimestamp()&#123;        Instant timestamp &#x3D; Instant.now();        System.out.println(&quot;What is value of this instant &quot; + timestamp);    &#125;</span><br><span class="line">    &#x2F;&#x2F; 使用预定义的格式化工具去解析或格式化日期    public void formateDate()&#123;        String dayAfterTommorrow &#x3D; &quot;20180210&quot;;        LocalDate formatted &#x3D; LocalDate.parse(dayAfterTommorrow, DateTimeFormatter.BASIC_ISO_DATE);        System.out.printf(&quot;Date generated from String %s is %s %n&quot;, dayAfterTommorrow, formatted);    &#125;</span><br><span class="line">    public static void main(String[] args) &#123;        DateTest dt &#x3D; new DateTest();</span><br><span class="line">        dt.formateDate();    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Java 8日期时间API的重点</p>
<p>1）提供了javax.time.ZoneId 获取时区。</p>
<p>2）提供了LocalDate和LocalTime类。</p>
<p>3）Java 8 的所有日期和时间API都是不可变类并且线程安全，而现有的Date和Calendar API中的java.util.Date和SimpleDateFormat是非线程安全的。</p>
<p>4）主包是 java.time,包含了表示日期、时间、时间间隔的一些类。里面有两个子包java.time.format用于格式化， java.time.temporal用于更底层的操作。</p>
<p>5）时区代表了地球上某个区域内普遍使用的标准时间。每个时区都有一个代号，格式通常由区域/城市构成（Asia/Tokyo），在加上与格林威治或 UTC的时差。例如：东京的时差是+09:00。</p>
<hr>
<p>（完）</p>
<p>*<strong>MarkerHub文章索引：</strong>（点击阅读原文直达）*</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https:&#x2F;&#x2F;github.com&#x2F;MarkerHub&#x2F;JavaIndex</span><br></pre></td></tr></table></figure> 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-develop/提高生产效率！3个常用的开源工具库分享"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/develop/%E6%8F%90%E9%AB%98%E7%94%9F%E4%BA%A7%E6%95%88%E7%8E%87%EF%BC%813%E4%B8%AA%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BC%80%E6%BA%90%E5%B7%A5%E5%85%B7%E5%BA%93%E5%88%86%E4%BA%AB/"
    >提高生产效率！3个常用的开源工具库分享.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/%E6%8F%90%E9%AB%98%E7%94%9F%E4%BA%A7%E6%95%88%E7%8E%87%EF%BC%813%E4%B8%AA%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BC%80%E6%BA%90%E5%B7%A5%E5%85%B7%E5%BA%93%E5%88%86%E4%BA%AB/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="提高生产效率！3个常用的开源工具库分享"><a href="#提高生产效率！3个常用的开源工具库分享" class="headerlink" title="提高生产效率！3个常用的开源工具库分享"></a>提高生产效率！3个常用的开源工具库分享</h1><p><strong>我们实际项目开发中是比较忌讳造轮子的，但是，自己在学习过程中造轮子绝对是对自己百利而无一害的！\</strong>造轮子是一种特别能够提高自己系统编程能力的手段。****</p>
<p>今天就分享几个我常用的开源工具库，希望对小伙伴们有帮助！</p>
<ol>
<li><strong>OSHI[1]</strong> ：一款为 Java 语言提供的基于 JNA 的（本机）操作系统和硬件信息库。</li>
<li><strong>EasyExcel[2]</strong> ：一款快速、简单避免 OOM 的 java 处理 Excel 工具。</li>
<li><strong>Hutool[3]</strong> : 一个非常实用的 Java 工具类库，对文件、流、加密解密、转码、正则、线程、XML 等 JDK 方法进行了封装。</li>
</ol>
<p>以下是较为详细一点的介绍，建议小伙伴们看完，方便自己快速上手，用在自己的项目中来提高生产效率。</p>
<h2 id="oshi"><a href="#oshi" class="headerlink" title="oshi"></a>oshi</h2><p><img src="http://iubest.gitee.io/pic/640-1601003797047.png" alt="img"></p>
<h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>OSHI 是一款为 Java 语言提供的基于 <strong>JNA</strong> 的（本机）操作系统和硬件信息库。</p>
<blockquote>
<p><strong>JNA(Java Native Access)</strong>[4]是一个开源的 Java 框架，是 Sun 公司推出的一种调用本地方法的技术，是建立在经典的 JNI 基础之上的一个框架。之所以说它是 JNI 的替代者，是因为 JNA 大大简化了调用本地方法的过程，使用很方便，基本上不需要脱离 Java 环境就可以完成。</p>
<p>JNI（Java Native Interface） 是 JDK 提供的一个编程接口，它允许 Java 程序调用其他语言编写的程序或者代码库，其实 JDK 本身的实现也大量用到 JNI 技术来调用本地 C 程序库。</p>
</blockquote>
<p><strong>通过 OSHI ，我们不需要安装任何其他本机库，就能查看内存和 CPU 使用率、磁盘和分区使用情况、设备、传感器等信息。</strong></p>
<p>OSHI 旨在提供一种跨平台的实现来检索系统信息，支持 Windows、Linux、MacOS、Unix 等主流操作系统。</p>
<p>官方是这样介绍 oshi 的：（翻译 Chrome 插件：<strong>Mate Translate</strong>）：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003797254.png" alt="img"></p>
<p>使用 oshi 你可以轻松制作出项目常用的系统监控功能，如下图所示：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003797252.png" alt="img"></p>
<h3 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h3><p>Maven</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;com.github.oshi&#x2F;oshi-core --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.github.oshi&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;oshi-core&lt;&#x2F;artifactId&gt;</span><br><span class="line">    &lt;version&gt;5.2.5&lt;&#x2F;version&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br></pre></td></tr></table></figure>

<p>Gradle</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;com.github.oshi&#x2F;oshi-core</span><br><span class="line">compile group: &#39;com.github.oshi&#39;, name: &#39;oshi-core&#39;, version: &#39;5.2.5&#39;</span><br></pre></td></tr></table></figure>

<h3 id="功能演示"><a href="#功能演示" class="headerlink" title="功能演示"></a>功能演示</h3><p>获取硬件信息对象<code>HardwareAbstractionLayer</code> ：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;系统信息</span><br><span class="line">SystemInfo si &#x3D; new SystemInfo();</span><br><span class="line">&#x2F;&#x2F;操作系统信息</span><br><span class="line">OperatingSystem os &#x3D; si.getOperatingSystem();</span><br><span class="line">&#x2F;&#x2F;硬件信息</span><br><span class="line">HardwareAbstractionLayer hal &#x3D; si.getHardware();</span><br></pre></td></tr></table></figure>

<p>有了代表硬件信息的对象<code>HardwareAbstractionLayer</code> 之后，我们就可以获取硬件相关的信息了！</p>
<p>下面简单演示一下获取内存和 CPU 相关信息。</p>
<p><strong>1.获取内存相关信息</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;内存相关信息</span><br><span class="line">GlobalMemory memory &#x3D; hal.getMemory();</span><br><span class="line">&#x2F;&#x2F;获取内存总容量</span><br><span class="line">String totalMemory &#x3D; FormatUtil.formatBytes(memory.getTotal());</span><br><span class="line">&#x2F;&#x2F;获取可用内存的容量</span><br><span class="line">String availableMemory &#x3D; FormatUtil.formatBytes(memory.getAvailable());</span><br></pre></td></tr></table></figure>

<p>有了内存总容量和内存可用容量，你就可以计算出当前内存的利用率了。</p>
<p><strong>2.获取 CPU 相关信息</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;CPU相关信息</span><br><span class="line">CentralProcessor processor &#x3D; hal.getProcessor();</span><br><span class="line">&#x2F;&#x2F;获取CPU名字</span><br><span class="line">String processorName &#x3D; processor.getProcessorIdentifier().getName();</span><br><span class="line">&#x2F;&#x2F;获取物理CPU数</span><br><span class="line">int physicalPackageCount &#x3D; processor.getPhysicalPackageCount();</span><br><span class="line">&#x2F;&#x2F;获取物理核心数</span><br><span class="line">int physicalProcessorCount &#x3D; processor.getPhysicalProcessorCount();</span><br></pre></td></tr></table></figure>

<h2 id="EasyExcel"><a href="#EasyExcel" class="headerlink" title="EasyExcel"></a>EasyExcel</h2><h3 id="介绍-1"><a href="#介绍-1" class="headerlink" title="介绍"></a>介绍</h3><p>Java 解析、生成 Excel 常用的框架有 Apache poi、jxl ，但是这两个框架使用起来都不够优雅，并且<strong>非常耗内存</strong>，<strong>严重时会导致内存溢出</strong>。</p>
<p><strong>怎么解决这个问题呢？</strong></p>
<p>推荐你使用阿里开源的 EasyExcel。正如这个项目官网介绍的那样，<strong>这是一款快速、简单避免 OOM 的 java 处理 Excel 工具。</strong></p>
<p>官方是这样介绍 EasyExcel 的：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003797351.png" alt="img"></p>
<h3 id="引入依赖-1"><a href="#引入依赖-1" class="headerlink" title="引入依赖"></a>引入依赖</h3><p>Maven</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;com.alibaba&#x2F;easyexcel --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;easyexcel&lt;&#x2F;artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.6&lt;&#x2F;version&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br></pre></td></tr></table></figure>

<p>Gradle</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;com.alibaba&#x2F;easyexcel</span><br><span class="line">compile group: &#39;com.alibaba&#39;, name: &#39;easyexcel&#39;, version: &#39;2.2.6&#39;</span><br></pre></td></tr></table></figure>

<h3 id="功能演示-1"><a href="#功能演示-1" class="headerlink" title="功能演示"></a>功能演示</h3><p>这里直接分享官方提供读取 Excel 的例子（<em>在实际项目中我对这部分做了简单的封装，涉及的地方比较多，就不分享出来了</em>）。</p>
<p><strong>实体对象</strong> （Excel 导入导出实体对象）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">@Data</span><br><span class="line">public class DemoData &#123;</span><br><span class="line">    private String string;</span><br><span class="line">    private Date date;</span><br><span class="line">    private Double doubleData;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>监听器</strong> （自定义 <code>AnalysisEventListener</code> 一次读取 5 条数据存储到数据库）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 有个很重要的点 DemoDataListener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去</span><br><span class="line">public class DemoDataListener extends AnalysisEventListener&lt;DemoData&gt; &#123;</span><br><span class="line">    private static final Logger LOGGER &#x3D; LoggerFactory.getLogger(DemoDataListener.class);</span><br><span class="line">    &#x2F;**</span><br><span class="line">     * 每隔5条存储数据库，实际使用中可以3000条，然后清理list ，方便内存回收</span><br><span class="line">     *&#x2F;</span><br><span class="line">    private static final int BATCH_COUNT &#x3D; 5;</span><br><span class="line">    List&lt;DemoData&gt; list &#x3D; new ArrayList&lt;DemoData&gt;();</span><br><span class="line">    &#x2F;**</span><br><span class="line">     * 假设这个是一个DAO，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。</span><br><span class="line">     *&#x2F;</span><br><span class="line">    private DemoDAO demoDAO;</span><br><span class="line">    public DemoDataListener() &#123;</span><br><span class="line">        &#x2F;&#x2F; 这里是demo，所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数</span><br><span class="line">        demoDAO &#x3D; new DemoDAO();</span><br><span class="line">    &#125;</span><br><span class="line">    &#x2F;**</span><br><span class="line">     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来</span><br><span class="line">     *</span><br><span class="line">     * @param demoDAO</span><br><span class="line">     *&#x2F;</span><br><span class="line">    public DemoDataListener(DemoDAO demoDAO) &#123;</span><br><span class="line">        this.demoDAO &#x3D; demoDAO;</span><br><span class="line">    &#125;</span><br><span class="line">    &#x2F;**</span><br><span class="line">     * 这个每一条数据解析都会来调用</span><br><span class="line">     *</span><br><span class="line">     * @param data</span><br><span class="line">     *            one row value. Is is same as &#123;@link AnalysisContext#readRowHolder()&#125;</span><br><span class="line">     * @param context</span><br><span class="line">     *&#x2F;</span><br><span class="line">    @Override</span><br><span class="line">    public void invoke(DemoData data, AnalysisContext context) &#123;</span><br><span class="line">        LOGGER.info(&quot;解析到一条数据:&#123;&#125;&quot;, JSON.toJSONString(data));</span><br><span class="line">        list.add(data);</span><br><span class="line">        &#x2F;&#x2F; 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM</span><br><span class="line">        if (list.size() &gt;&#x3D; BATCH_COUNT) &#123;</span><br><span class="line">            saveData();</span><br><span class="line">            &#x2F;&#x2F; 存储完成清理 list</span><br><span class="line">            list.clear();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    &#x2F;**</span><br><span class="line">     * 所有数据解析完成了 都会来调用</span><br><span class="line">     *</span><br><span class="line">     * @param context</span><br><span class="line">     *&#x2F;</span><br><span class="line">    @Override</span><br><span class="line">    public void doAfterAllAnalysed(AnalysisContext context) &#123;</span><br><span class="line">        &#x2F;&#x2F; 这里也要保存数据，确保最后遗留的数据也存储到数据库</span><br><span class="line">        saveData();</span><br><span class="line">        LOGGER.info(&quot;所有数据解析完成！&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">    &#x2F;**</span><br><span class="line">     * 加上存储数据库</span><br><span class="line">     *&#x2F;</span><br><span class="line">    private void saveData() &#123;</span><br><span class="line">        LOGGER.info(&quot;&#123;&#125;条数据，开始存储数据库！&quot;, list.size());</span><br><span class="line">        demoDAO.save(list);</span><br><span class="line">        LOGGER.info(&quot;存储数据库成功！&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>持久层</strong> (mybatis 或者 jpa 来做都行)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;**</span><br><span class="line"> * 假设这个是你的DAO存储。当然还要这个类让spring管理，当然你不用需要存储，也不需要这个类。</span><br><span class="line"> **&#x2F;</span><br><span class="line">public class DemoDAO &#123;</span><br><span class="line">    public void save(List&lt;DemoData&gt; list) &#123;</span><br><span class="line">        &#x2F;&#x2F; 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入</span><br><span class="line">        System.out.println(list);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>读取数据</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">String fileName &#x3D; &quot;src&#x2F;test&#x2F;resources&#x2F;demo&#x2F;demo.xlsx&quot;;</span><br><span class="line">&#x2F;&#x2F; 这里 需要指定读用哪个class去读，然后读取第一个sheet 文件流会自动关闭</span><br><span class="line">EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();</span><br></pre></td></tr></table></figure>

<p><strong>输出结果</strong> （已过滤非必要数据）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">2020-09-16 08:14:33.727 DEBUG [main] com.alibaba.excel.context.AnalysisContextImpl:91 - Began to read：ReadSheetHolder&#123;sheetNo&#x3D;0, sheetName&#x3D;&#39;Sheet1&#39;&#125; com.alibaba.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder@6f3b5d16</span><br><span class="line">2020-09-16 08:14:33.870 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1577811661000,&quot;doubleData&quot;:1.0,&quot;string&quot;:&quot;字符串0&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.870 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1577898061000,&quot;doubleData&quot;:2.0,&quot;string&quot;:&quot;字符串1&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.871 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1577984461000,&quot;doubleData&quot;:3.0,&quot;string&quot;:&quot;字符串2&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.871 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578070861000,&quot;doubleData&quot;:4.0,&quot;string&quot;:&quot;字符串3&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.872 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578157261000,&quot;doubleData&quot;:5.0,&quot;string&quot;:&quot;字符串4&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.872 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:80 - 5条数据，开始存储数据库！</span><br><span class="line">[DemoData(string&#x3D;字符串0, date&#x3D;Wed Jan 01 01:01:01 CST 2020, doubleData&#x3D;1.0), DemoData(string&#x3D;字符串1, date&#x3D;Thu Jan 02 01:01:01 CST 2020, doubleData&#x3D;2.0), DemoData(string&#x3D;字符串2, date&#x3D;Fri Jan 03 01:01:01 CST 2020, doubleData&#x3D;3.0), DemoData(string&#x3D;字符串3, date&#x3D;Sat Jan 04 01:01:01 CST 2020, doubleData&#x3D;4.0), DemoData(string&#x3D;字符串4, date&#x3D;Sun Jan 05 01:01:01 CST 2020, doubleData&#x3D;5.0)]</span><br><span class="line">2020-09-16 08:14:33.874 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:82 - 存储数据库成功！</span><br><span class="line">2020-09-16 08:14:33.875 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578243661000,&quot;doubleData&quot;:6.0,&quot;string&quot;:&quot;字符串5&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.875 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578330061000,&quot;doubleData&quot;:7.0,&quot;string&quot;:&quot;字符串6&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.876 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578416461000,&quot;doubleData&quot;:8.0,&quot;string&quot;:&quot;字符串7&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.876 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578502861000,&quot;doubleData&quot;:9.0,&quot;string&quot;:&quot;字符串8&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.876 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:54 - 解析到一条数据:&#123;&quot;date&quot;:1578589261000,&quot;doubleData&quot;:10.0,&quot;string&quot;:&quot;字符串9&quot;&#125;</span><br><span class="line">2020-09-16 08:14:33.877 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:80 - 5条数据，开始存储数据库！</span><br><span class="line">[DemoData(string&#x3D;字符串5, date&#x3D;Mon Jan 06 01:01:01 CST 2020, doubleData&#x3D;6.0), DemoData(string&#x3D;字符串6, date&#x3D;Tue Jan 07 01:01:01 CST 2020, doubleData&#x3D;7.0), DemoData(string&#x3D;字符串7, date&#x3D;Wed Jan 08 01:01:01 CST 2020, doubleData&#x3D;8.0), DemoData(string&#x3D;字符串8, date&#x3D;Thu Jan 09 01:01:01 CST 2020, doubleData&#x3D;9.0), DemoData(string&#x3D;字符串9, date&#x3D;Fri Jan 10 01:01:01 CST 2020, doubleData&#x3D;10.0)]</span><br><span class="line">2020-09-16 08:14:33.877 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:82 - 存储数据库成功！</span><br><span class="line">2020-09-16 08:14:33.877 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:80 - 0条数据，开始存储数据库！</span><br><span class="line">[]</span><br><span class="line">2020-09-16 08:14:33.877 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:82 - 存储数据库成功！</span><br><span class="line">2020-09-16 08:14:33.877 INFO [main] com.alibaba.easyexcel.test.demo.read.DemoDataListener:73 - 所有数据解析完成！</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure>

<h2 id="Hutool"><a href="#Hutool" class="headerlink" title="Hutool"></a>Hutool</h2><p><img src="http://iubest.gitee.io/pic/640-1601003797238.png" alt="img"></p>
<h3 id="介绍-2"><a href="#介绍-2" class="headerlink" title="介绍"></a>介绍</h3><p><strong>Hutool 是一个非常实用的 Java 工具类库，对文件、流、加密解密、转码、正则、线程、XML 等 JDK 方法进行了封装。</strong></p>
<p>非常使用的开源工具类库，推荐小伙伴们在自己项目中使用。</p>
<p>官方是这样介绍 Hutool 的：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601003797106.png" alt="img"></p>
<h3 id="引入依赖-2"><a href="#引入依赖-2" class="headerlink" title="引入依赖"></a>引入依赖</h3><p>Maven</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;cn.hutool&#x2F;hutool-all --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;cn.hutool&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;hutool-all&lt;&#x2F;artifactId&gt;</span><br><span class="line">    &lt;version&gt;5.4.2&lt;&#x2F;version&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br></pre></td></tr></table></figure>

<p>Gradle</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;cn.hutool&#x2F;hutool-all</span><br><span class="line">compile group: &#39;cn.hutool&#39;, name: &#39;hutool-all&#39;, version: &#39;5.4.2&#39;</span><br></pre></td></tr></table></figure>

<h3 id="功能演示-2"><a href="#功能演示-2" class="headerlink" title="功能演示"></a>功能演示</h3><p>简单演示几个比较实用的功能。</p>
<h4 id="邮件"><a href="#邮件" class="headerlink" title="邮件"></a>邮件</h4><p>在 Java 中发送邮件主要品依靠 <code>javax.mail</code> 包，但是由于使用比较繁琐，因此 Hutool 针对其做了封装。由于依赖第三方包，因此将此工具类归类到 extra 模块中。</p>
<p>实际项目中可以自定义邮箱配置，然后让配置保存在数据库，并在缓存中保存一份。</p>
<p>如果不想自定义配置的话，直接在配置文件中把邮箱配置写死就行了。</p>
<p>有了邮件的相关配置之后，定义邮件服务器之后即可发送邮件，非常方便：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">MailAccount account &#x3D; new MailAccount();</span><br><span class="line">account.setHost(&quot;smtp.yeah.net&quot;);</span><br><span class="line">account.setPort(&quot;25&quot;);</span><br><span class="line">account.setAuth(true);</span><br><span class="line">account.setFrom(&quot;hutool@yeah.net&quot;);</span><br><span class="line">account.setUser(&quot;hutool&quot;);</span><br><span class="line">account.setPass(&quot;q1w2e3&quot;);</span><br><span class="line"></span><br><span class="line">MailUtil.send(account, CollUtil.newArrayList(&quot;hutool@foxmail.com&quot;), &quot;测试&quot;, &quot;邮件来自Hutool测试&quot;, false);</span><br></pre></td></tr></table></figure>

<h4 id="唯一-ID"><a href="#唯一-ID" class="headerlink" title="唯一 ID"></a>唯一 ID</h4><p>在分布式环境中，唯一 ID 生成应用十分广泛，生成方法也多种多样，Hutool 针对一些常用生成策略做了简单封装。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601167847698.png" alt="img"></p>
<p><a target="_blank" rel="noopener" href="https://juejin.im/post/6844903935296176141">https://juejin.im/post/6844903935296176141</a></p>
<p>Hutool 提供的唯一 ID 生成器的工具类，涵盖了：</p>
<ul>
<li><strong>UUID</strong></li>
<li><strong>ObjectId</strong>（MongoDB）</li>
<li><strong>Snowflake</strong>（Twitter）</li>
</ul>
<p>拿 UUID 举例！</p>
<p>Hutool 重写<code>java.util.UUID</code>的逻辑，对应类为<code>cn.hutool.core.lang.UUID</code>，使生成不带-的 UUID 字符串不再需要做字符替换，<strong>性能提升一倍左右</strong>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;生成的UUID是带-的字符串，类似于：a5c8a5e8-df2b-4706-bea4-08d0939410e3</span><br><span class="line">String uuid &#x3D; IdUtil.randomUUID();</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;生成的是不带-的字符串，类似于：b17f24ff026d40949c85a24f4f375d42</span><br><span class="line">String simpleUUID &#x3D; IdUtil.simpleUUID();</span><br></pre></td></tr></table></figure>

<h4 id="Http-请求工具类"><a href="#Http-请求工具类" class="headerlink" title="Http 请求工具类"></a>Http 请求工具类</h4><p>针对最为常用的 GET 和 POST 请求，HttpUtil 封装了两个方法，</p>
<ul>
<li><code>HttpUtil.get</code></li>
<li><code>HttpUtil.post</code></li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 最简单的HTTP请求，可以自动通过header等信息判断编码，不区分HTTP和HTTPS</span><br><span class="line">String result1 &#x3D; HttpUtil.get(&quot;https:&#x2F;&#x2F;www.baidu.com&quot;);</span><br><span class="line">&#x2F;&#x2F; 当无法识别页面编码的时候，可以自定义请求页面的编码</span><br><span class="line">String result2 &#x3D; HttpUtil.get(&quot;https:&#x2F;&#x2F;www.baidu.com&quot;, CharsetUtil.CHARSET_UTF_8);</span><br><span class="line">&#x2F;&#x2F;可以单独传入http参数，这样参数会自动做URL编码，拼接在URL中</span><br><span class="line">HashMap&lt;String, Object&gt; paramMap &#x3D; new HashMap&lt;&gt;();</span><br><span class="line">paramMap.put(&quot;city&quot;, &quot;北京&quot;);</span><br><span class="line">String result3 &#x3D; HttpUtil.get(&quot;https:&#x2F;&#x2F;www.baidu.com&quot;, paramMap);</span><br></pre></td></tr></table></figure>

<h4 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h4><p>Hutool 提供了常见的几种缓存策略的实现：</p>
<ol>
<li><strong>FIFO(first in first out)</strong> ：先进先出策略。</li>
<li><strong>LFU(least frequently used)</strong> ：最少使用率策略。</li>
<li><strong>LFU(least frequently used)</strong> ：最少使用率策略。</li>
<li>定时缓存 ：对被缓存的对象定义一个过期时间，当对象超过过期时间会被清理。</li>
<li>……</li>
</ol>
<p>FIFO(first in first out) 策略缓存使用:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Cache&lt;String,String&gt; fifoCache &#x3D; CacheUtil.newFIFOCache(3);</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;加入元素，每个元素可以设置其过期时长，DateUnit.SECOND.getMillis()代表每秒对应的毫秒数，在此为3秒</span><br><span class="line">fifoCache.put(&quot;key1&quot;, &quot;value1&quot;, DateUnit.SECOND.getMillis() * 3);</span><br><span class="line">fifoCache.put(&quot;key2&quot;, &quot;value2&quot;, DateUnit.SECOND.getMillis() * 3);</span><br><span class="line">fifoCache.put(&quot;key3&quot;, &quot;value3&quot;, DateUnit.SECOND.getMillis() * 3);</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;由于缓存容量只有3，当加入第四个元素的时候，根据FIFO规则，最先放入的对象将被移除</span><br><span class="line">fifoCache.put(&quot;key4&quot;, &quot;value4&quot;, DateUnit.SECOND.getMillis() * 3);</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;value1为null</span><br><span class="line">String value1 &#x3D; fifoCache.get(&quot;key1&quot;);</span><br></pre></td></tr></table></figure>

<h4 id="控制台打印封装-Console"><a href="#控制台打印封装-Console" class="headerlink" title="控制台打印封装-Console"></a>控制台打印封装-Console</h4><p>一般情况下，我们打印信息到控制台小伙伴们应该再熟悉不过了！</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(&quot;Hello World&quot;);</span><br></pre></td></tr></table></figure>

<p>但是，这种方式不满足很多场景的需要：</p>
<ol>
<li>不支持参数，对象打印需要拼接字符串</li>
<li>不能直接打印数组，需要手动调用<code>Arrays.toString</code></li>
</ol>
<p>为此，Hutool 封装了<code>Console</code>对象。</p>
<blockquote>
<p>Console 对象的使用更加类似于 Javascript 的<code>console.log()</code>方法，这也是借鉴了 JS 的一个语法糖。</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">String[] a &#x3D; &#123;&quot;java&quot;, &quot;c++&quot;, &quot;c&quot;&#125;;</span><br><span class="line">Console.log(a);&#x2F;&#x2F;控制台输出：[java, c++, c]</span><br><span class="line"></span><br><span class="line">Console.log(&quot;This is Console log for &#123;&#125;.&quot;, &quot;test&quot;);&#x2F;&#x2F;控制台输出：This is Console log for test.</span><br></pre></td></tr></table></figure>

<h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p>[1]oshi: <em><a target="_blank" rel="noopener" href="https://github.com/oshi/oshi">https://github.com/oshi/oshi</a></em>[2]EasyExcel: <em><a target="_blank" rel="noopener" href="https://github.com/alibaba/easyexcel">https://github.com/alibaba/easyexcel</a></em>[3]Hutool: <em><a target="_blank" rel="noopener" href="https://github.com/looly/hutool">https://github.com/looly/hutool</a></em>[4]JNA(Java Native Access): <em><a target="_blank" rel="noopener" href="https://github.com/java-native-access/jna">https://github.com/java-native-access/jna</a></em></p>
<h2 id="闲聊"><a href="#闲聊" class="headerlink" title="闲聊"></a>闲聊</h2><p>其实今天本来是准备更新一篇系统设计面试指南的，毕竟系统设计几乎也是面试必问了，非常重要。 最近询问这方面问题的小伙伴比较多。</p>
<p>文章大概要写啥内容都构思好了，结果还是过于天真。因为涉及到的内容和知识点太多了。预计会在下周分享（如果中途没出啥状况的话）。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
  </article>
  

  
  <nav class="page-nav">
    
    <a class="extend prev" rel="prev" href="/page/8/">上一页</a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/7/">7</a><a class="page-number" href="/page/8/">8</a><span class="page-number current">9</span><a class="page-number" href="/page/10/">10</a><a class="page-number" href="/page/11/">11</a><span class="space">&hellip;</span><a class="page-number" href="/page/14/">14</a><a class="extend next" rel="next" href="/page/10/">下一页</a>
  </nav>
  
</section>
</div>

      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> TzWind
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="Hexo"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://www.baidu.com">百度</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>